Skill: hansondong/pao-system (version 1.1.2)

--- name: pao-system description: PAO系统 - 个人AI运营助手。触发词：PAO、Personal AI Operator、运营助手、技能管理、协议管理、WebSocket通信、PAO技能 version: 1.1.2 author: CodeBuddy tags: - pao - ai-operator - skill-management - websocket - protocol --- # PAO System Skill PAO（Personal AI Operator）系统是一个智能运营助手框架，提供技能管理、协议处理、WebSocket通信等核心功能。 ## 核心功能 - **技能管理 (Skill Management)**: 自动注册、加载、演化AI技能 - **协议处理 (Protocol Handling)**: 支持多种通信协议（WebSocket、HTTP等） - **系统集成 (System Integration)**: 与外部系统无缝集成 - **任务自动化 (Task Automation)**: 自动化执行复杂任务流程 ## 使用方式 ```bash # 启动PAO系统 python -m pao_system # 运行技能管理示例 python examples/run_skill_example.py # 运行WebSocket客户端示例 python examples/run_websocket_client.py ``` ## 依赖项 - Python 3.8+ - websockets - asyncio - pydantic ## 目录结构 ``` pao-system/ ├── src/ │ ├── skill_manager.py # 技能管理核心 │ ├── protocol_handler.py # 协议处理 │ ├── system_integrator.py # 系统集成 │ └── protocols/ # 协议实现 ├── tests/ # 测试文件 ├── examples/ # 示例代码 └── config/ # 配置文件 ```

path: build/lib/core/communication.py
content: """
Communication Module - 通信模块

提供设备间的消息传递功能
"""

import asyncio
import json
import logging
from typing import Dict, Optional, Callable, Any
from dataclasses import dataclass
from enum import Enum

logger = logging.getLogger(__name__)


class MessageType(str, Enum):
    """消息类型"""
    TEXT = "text"
    SYNC = "sync"
    COMMAND = "command"
    RESPONSE = "response"
    HEARTBEAT = "heartbeat"


@dataclass
class Message:
    """消息"""
    msg_id: str
    msg_type: MessageType
    sender: str
    receiver: str
    content: Any
    timestamp: float


class Communication:
    """
    通信管理器

    负责设备间的消息发送和接收
    """

    def __init__(self, device_id: str, config=None):
        self.device_id = device_id
        self.config = config
        self.connections: Dict[str, asyncio.StreamWriter] = {}
        self.message_handlers: Dict[MessageType, Callable] = {}
        self._running = False
        self._heartbeat_task: Optional[asyncio.Task] = None

    async def connect_to(self, peer_id: str, host: str = "localhost", port: int = 8765) -> bool:
        """
        连接到对等节点

        Args:
            peer_id: 对等节点ID
            host: 主机地址
            port: 端口

        Returns:
            bool: 连接是否成功
        """
        try:
            reader, writer = await asyncio.open_connection(host, port)
            self.connections[peer_id] = writer
            logger.info(f"已连接到 {peer_id}")
            return True
        except Exception as e:
            logger.error(f"连接失败: {e}")
            return False

    async def disconnect(self):
        """断开所有连接"""
        self._running = False

        for peer_id, writer in self.connections.items():
            writer.close()
            await writer.wait_closed()

        self.connections.clear()
        logger.info("已断开所有连接")

    async def disconnect_peer(self, peer_id: str):
        """断开指定对等节点的连接"""
        if peer_id in self.connections:
            writer = self.connections[peer_id]
            writer.close()
            await writer.wait_closed()
            del self.connections[peer_id]
            logger.info(f"已断开与 {peer_id} 的连接")

    async def send_message(self, peer_id: str, content: Any,
                          msg_type: MessageType = MessageType.TEXT) -> bool:
        """
        发送消息

        Args:
            peer_id: 对等节点ID
            content: 消息内容
            msg_type: 消息类型

        Returns:
            bool: 发送是否成功
        """
        if peer_id not in self.connections:
            logger.warning(f"未连接到 {peer_id}")
            return False

        try:
            message = Message(
                msg_id=f"{self.device_id}_{asyncio.get_event_loop().time()}",
                msg_type=msg_type,
                sender=self.device_id,
                receiver=peer_id,
                content=content,
                timestamp=asyncio.get_event_loop().time()
            )

            data = json.dumps({
                "msg_id": message.msg_id,
                "msg_type": message.msg_type.value,
                "sender": message.sender,
                "receiver": message.receiver,
                "content": message.content,
                "timestamp": message.timestamp
            })

            writer = self.connections[peer_id]
            writer.write(data.encode())
            await writer.drain()
            return True

        except Exception as e:
            logger.error(f"发送消息失败: {e}")
            return False

    def register_handler(self, msg_type: MessageType, handler: Callable):
        """注册消息处理器"""
        self.message_handlers[msg_type] = handler

    async def start_heartbeat(self, interval: int = 30):
        """启动心跳"""
        self._running = True

        async def heartbeat():
            while self._running:
                await asyncio.sleep(interval)
                for peer_id in self.connections:
                    await self.send_message(peer_id, {}, MessageType.HEARTBEAT)

        self._heartbeat_task = asyncio.create_task(heartbeat())

    def get_connected_peers(self) -> list:
        """获取已连接的节点列表"""
        return list(self.connections.keys())

    def is_connected(self, peer_id: str) -> bool:
        """检查是否连接到指定节点"""
        return peer_id in self.connections


path: build/lib/core/config.py
content: """
配置管理模块
管理 PAO 系统的配置和设置
"""

import os
import json
import logging
from pathlib import Path
from typing import Dict, Any, Optional
from dataclasses import dataclass, field, asdict
from enum import Enum

import yaml
from pydantic import BaseModel, Field


class LogLevel(str, Enum):
    """日志级别枚举"""
    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


class NetworkMode(str, Enum):
    """网络模式枚举"""
    LAN_ONLY = "lan_only"          # 仅局域网
    P2P_RELAY = "p2p_relay"        # P2P带中继
    CLOUD_SYNC = "cloud_sync"      # 云同步


class PrivacyLevel(str, Enum):
    """隐私级别枚举"""
    MINIMAL = "minimal"           # 最小隐私，共享基本信息
    STANDARD = "standard"         # 标准隐私，加密通信
    STRICT = "strict"             # 严格隐私，本地优先
    MAXIMUM = "maximum"           # 最大隐私，无网络


@dataclass
class NetworkConfig:
    """网络配置"""
    mode: NetworkMode = NetworkMode.LAN_ONLY
    discovery_port: int = 8765
    data_port: int = 8766
    relay_servers: list = field(default_factory=lambda: [
        "relay1.pao-system.dev:8767",
        "relay2.pao-system.dev:8767"
    ])
    max_connections: int = 10
    connection_timeout: int = 30  # 秒
    heartbeat_interval: int = 60  # 秒
    enable_upnp: bool = True
    enable_ipv6: bool = True


@dataclass
class SecurityConfig:
    """安全配置"""
    privacy_level: PrivacyLevel = PrivacyLevel.STANDARD
    encryption_enabled: bool = True
    encryption_algorithm: str = "AES256-GCM"
    require_authentication: bool = True
    authentication_timeout: int = 300  # 秒
    max_auth_attempts: int = 3
    enable_firewall: bool = True
    allowed_networks: list = field(default_factory=lambda: [
        "192.168.0.0/16",
        "10.0.0.0/8",
        "172.16.0.0/12"
    ])


@dataclass
class StorageConfig:
    """存储配置"""
    type: str = "sqlite"  # 存储类型
    path: str = field(default_factory=lambda: str(Path.home() / ".pao" / "data" / "pao.db"))
    data_dir: str = field(default_factory=lambda: str(Path.home() / ".pao" / "data"))
    table_name: str = "memories"
    max_storage_mb: int = 1024  # MB
    backup_enabled: bool = True
    backup_interval: int = 3600  # 秒
    backup_count: int = 7
    compression_enabled: bool = True
    encryption_at_rest: bool = True


@dataclass
class MemoryConfig:
    """记忆系统配置"""
    enable_persistent_memory: bool = True
    memory_ttl_days: int = 30  # 记忆保存天数
    max_memory_items: int = 10000
    memory_compression: bool = True
    enable_semantic_search: bool = True
    semantic_search_threshold: float = 0.7
    enable_context_association: bool = True
    context_window_size: int = 10


@dataclass
class LoggingConfig:
    """日志配置"""
    level: LogLevel = LogLevel.INFO
    enable_file_logging: bool = True
    log_dir: str = field(default_factory=lambda: str(Path.home() / ".pao" / "logs"))
    max_log_size_mb: int = 10
    log_backup_count: int = 5
    enable_json_logs: bool = False
    enable_console_logging: bool = True


@dataclass
class PerformanceConfig:
    """性能配置"""
    max_workers: int = 4
    worker_timeout: int = 300  # 秒
    cache_size_mb: int = 100
    enable_gzip: bool = True
    enable_connection_pooling: bool = True
    pool_size: int = 5
    pool_timeout: int = 30  # 秒


class PAOConfig(BaseModel):
    """PAO 系统主配置"""
    
    # 基本配置
    device_name: str = Field(default_factory=lambda: f"pao-{os.getlogin()}")
    device_id: str = Field(default_factory=lambda: str(hash(os.getlogin() + os.environ.get('COMPUTERNAME', ''))))
    version: str = "0.1.0"
    
    # 子系统配置
    network: NetworkConfig = Field(default_factory=NetworkConfig)
    security: SecurityConfig = Field(default_factory=SecurityConfig)
    storage: StorageConfig = Field(default_factory=StorageConfig)
    memory: MemoryConfig = Field(default_factory=MemoryConfig)
    logging: LoggingConfig = Field(default_factory=LoggingConfig)
    performance: PerformanceConfig = Field(default_factory=PerformanceConfig)
    
    # 功能开关
    enable_discovery: bool = True
    enable_sync: bool = True
    enable_memory_sharing: bool = True
    enable_skill_evolution: bool = False  # 第一阶段不启用
    enable_context_awareness: bool = False  # 第一阶段不启用
    
    # 高级设置
    auto_start: bool = False
    debug_mode: bool = False
    developer_mode: bool = False
    
    class Config:
        json_encoders = {
            # 处理枚举类型
            LogLevel: lambda v: v.value,
            NetworkMode: lambda v: v.value,
            PrivacyLevel: lambda v: v.value
        }


class ConfigManager:
    """配置管理器"""
    
    def __init__(self, config_dir: Optional[str] = None):
        if config_dir is None:
            config_dir = str(Path.home() / ".pao")
        
        self.config_dir = Path(config_dir)
        self.config_file = self.config_dir / "config.yaml"
        self.secrets_file = self.config_dir / "secrets.yaml"
        
        # 确保目录存在
        self.config_dir.mkdir(parents=True, exist_ok=True)
        
        # 加载配置
        self.config = self._load_or_create_config()
        self.secrets = self._load_or_create_secrets()
    
    def _load_or_create_config(self) -> PAOConfig:
        """加载或创建配置"""
        if self.config_file.exists():
            try:
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config_data = yaml.safe_load(f) or {}
                return PAOConfig(**config_data)
            except Exception as e:
                logging.warning(f"加载配置文件失败，使用默认配置: {e}")
                return PAOConfig()
        else:
            config = PAOConfig()
            self.save_config(config)
            return config
    
    def _load_or_create_secrets(self) -> Dict[str, Any]:
        """加载或创建密钥文件"""
        if self.secrets_file.exists():
            try:
                with open(self.secrets_file, 'r', encoding='utf-8') as f:
                    return yaml.safe_load(f) or {}
            except Exception as e:
                logging.warning(f"加载密钥文件失败: {e}")
                return {}
        else:
            secrets = self._generate_default_secrets()
            self.save_secrets(secrets)
            return secrets
    
    def _generate_default_secrets(self) -> Dict[str, Any]:
        """生成默认密钥"""
        import secrets
        import base64
        
        return {
            "encryption_key": base64.b64encode(secrets.token_bytes(32)).decode('utf-8'),
            "auth_token": secrets.token_hex(32),
            "device_secret": secrets.token_hex(16),
            "relay_tokens": {}
        }
    
    def save_config(self, config: Optional[PAOConfig] = None) -> None:
        """保存配置"""
        if config is None:
            config = self.config
        
        # 转换为字典
        config_dict = config.dict()
        
        # 保存到文件
        with open(self.config_file, 'w', encoding='utf-8') as f:
            yaml.dump(config_dict, f, default_flow_style=False, allow_unicode=True, indent=2)
        
        logging.info(f"配置已保存到: {self.config_file}")
    
    def save_secrets(self, secrets: Optional[Dict[str, Any]] = None) -> None:
        """保存密钥"""
        if secrets is None:
            secrets = self.secrets
        
        # 保存到文件
        with open(self.secrets_file, 'w', encoding='utf-8') as f:
            yaml.dump(secrets, f, default_flow_style=False, allow_unicode=True)
        
        # 设置文件权限（仅在Unix系统）
        if os.name != 'nt':
            os.chmod(self.secrets_file, 0o600)
        
        logging.info(f"密钥已保存到: {self.secrets_file}")
    
    def update_config(self, updates: Dict[str, Any]) -> PAOConfig:
        """更新配置"""
        # 深度合并更新
        import copy
        
        current_dict = self.config.dict()
        updated_dict = self._deep_merge(current_dict, updates)
        
        # 创建新配置
        self.config = PAOConfig(**updated_dict)
        self.save_config()
        
        return self.config
    
    def _deep_merge(self, base: Dict[str, Any], updates: Dict[str, Any]) -> Dict[str, Any]:
        """深度合并字典"""
        result = copy.deepcopy(base)
        
        for key, value in updates.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = self._deep_merge(result[key], value)
            else:
                result[key] = value
        
        return result
    
    def get_config_path(self, *args) -> Path:
        """获取配置目录下的路径"""
        return self.config_dir.joinpath(*args)
    
    def get_data_path(self, *args) -> Path:
        """获取数据目录下的路径"""
        data_dir = Path(self.config.storage.data_dir)
        data_dir.mkdir(parents=True, exist_ok=True)
        return data_dir.joinpath(*args)
    
    def get_log_path(self, *args) -> Path:
        """获取日志目录下的路径"""
        log_dir = Path(self.config.logging.log_dir)
        log_dir.mkdir(parents=True, exist_ok=True)
        return log_dir.joinpath(*args)
    
    def setup_logging(self) -> None:
        """设置日志系统"""
        import sys
        
        log_config = self.config.logging
        
        # 配置根日志记录器
        root_logger = logging.getLogger()
        root_logger.setLevel(getattr(logging, log_config.level.value))
        
        # 清除现有处理器
        root_logger.handlers.clear()
        
        # 控制台处理器
        if log_config.enable_console_logging:
            console_handler = logging.StreamHandler(sys.stdout)
            console_handler.setLevel(getattr(logging, log_config.level.value))
            console_format = logging.Formatter(
                '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            )
            console_handler.setFormatter(console_format)
            root_logger.addHandler(console_handler)
        
        # 文件处理器
        if log_config.enable_file_logging:
            log_file = self.get_log_path("pao.log")
            file_handler = logging.FileHandler(log_file, encoding='utf-8')
            file_handler.setLevel(getattr(logging, log_config.level.value))
            
            if log_config.enable_json_logs:
                import json_log_formatter
                formatter = json_log_formatter.JSONFormatter()
            else:
                formatter = logging.Formatter(
                    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
                )
            
            file_handler.setFormatter(formatter)
            root_logger.addHandler(file_handler)


def load_config(config_dir: Optional[str] = None) -> PAOConfig:
    """快速加载配置"""
    manager = ConfigManager(config_dir)
    return manager.config


def get_config_manager(config_dir: Optional[str] = None) -> ConfigManager:
    """获取配置管理器实例"""
    return ConfigManager(config_dir)


def create_default_config() -> PAOConfig:
    """创建默认配置"""
    return PAOConfig()


if __name__ == "__main__":
    # 测试配置管理
    config = create_default_config()
    print("默认配置:", json.dumps(config.dict(), indent=2, ensure_ascii=False))
    
    # 测试配置管理器
    manager = get_config_manager()
    print(f"配置目录: {manager.config_dir}")
    print(f"设备名称: {manager.config.device_name}")

path: build/lib/core/device.py
content: """
设备模型定义
定义 PAO 系统中的设备实体和状态
"""

import uuid
import socket
from datetime import datetime
from typing import Dict, Any, Optional, List
from enum import Enum
from dataclasses import dataclass, field
from pydantic import BaseModel, Field


class DeviceType(str, Enum):
    """设备类型枚举"""
    DESKTOP = "desktop"
    LAPTOP = "laptop"
    PHONE = "phone"
    TABLET = "tablet"
    SERVER = "server"
    IOT = "iot"
    UNKNOWN = "unknown"


class DeviceStatus(str, Enum):
    """设备状态枚举"""
    ONLINE = "online"
    OFFLINE = "offline"
    BUSY = "busy"
    IDLE = "idle"
    SLEEPING = "sleeping"


class DeviceCapability(str, Enum):
    """设备能力枚举"""
    COMPUTE = "compute"          # 计算能力
    STORAGE = "storage"          # 存储能力
    MEMORY = "memory"           # 记忆存储
    DISPLAY = "display"         # 显示能力
    INPUT = "input"             # 输入能力
    NETWORK = "network"         # 网络能力
    SENSORS = "sensors"         # 传感器能力


@dataclass
class DeviceInfo:
    """设备基本信息"""
    device_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    name: str = "Unknown Device"
    device_type: DeviceType = DeviceType.UNKNOWN
    hostname: str = field(default_factory=socket.gethostname)
    ip_address: str = field(default_factory=lambda: socket.gethostbyname(socket.gethostname()))
    mac_address: str = ""
    os_name: str = ""
    os_version: str = ""
    
    # 设备能力
    capabilities: List[DeviceCapability] = field(default_factory=list)
    
    # 资源信息
    cpu_cores: int = 0
    memory_gb: int = 0
    storage_gb: int = 0
    
    # 网络信息
    port: int = 8765  # 默认通信端口
    protocol_version: str = "1.0.0"
    
    # 元数据
    first_seen: datetime = field(default_factory=datetime.now)
    last_seen: datetime = field(default_factory=datetime.now)
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return {
            "device_id": self.device_id,
            "name": self.name,
            "device_type": self.device_type.value,
            "hostname": self.hostname,
            "ip_address": self.ip_address,
            "mac_address": self.mac_address,
            "os_name": self.os_name,
            "os_version": self.os_version,
            "capabilities": [cap.value for cap in self.capabilities],
            "cpu_cores": self.cpu_cores,
            "memory_gb": self.memory_gb,
            "storage_gb": self.storage_gb,
            "port": self.port,
            "protocol_version": self.protocol_version,
            "first_seen": self.first_seen.isoformat(),
            "last_seen": self.last_seen.isoformat()
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "DeviceInfo":
        """从字典创建"""
        device = cls()
        device.device_id = data.get("device_id", device.device_id)
        device.name = data.get("name", device.name)
        device.device_type = DeviceType(data.get("device_type", DeviceType.UNKNOWN.value))
        device.hostname = data.get("hostname", device.hostname)
        device.ip_address = data.get("ip_address", device.ip_address)
        device.mac_address = data.get("mac_address", device.mac_address)
        device.os_name = data.get("os_name", device.os_name)
        device.os_version = data.get("os_version", device.os_version)
        
        # 处理能力列表
        capabilities = data.get("capabilities", [])
        device.capabilities = [DeviceCapability(cap) for cap in capabilities]
        
        device.cpu_cores = data.get("cpu_cores", device.cpu_cores)
        device.memory_gb = data.get("memory_gb", device.memory_gb)
        device.storage_gb = data.get("storage_gb", device.storage_gb)
        device.port = data.get("port", device.port)
        device.protocol_version = data.get("protocol_version", device.protocol_version)
        
        # 时间戳
        if "first_seen" in data:
            device.first_seen = datetime.fromisoformat(data["first_seen"])
        if "last_seen" in data:
            device.last_seen = datetime.fromisoformat(data["last_seen"])
        
        return device
    
    def update_last_seen(self):
        """更新最后出现时间"""
        self.last_seen = datetime.now()
    
    def get_capability_score(self, capability: DeviceCapability) -> int:
        """获取设备特定能力的评分"""
        if capability not in self.capabilities:
            return 0
        
        # 根据能力类型和设备规格打分
        scores = {
            DeviceCapability.COMPUTE: self.cpu_cores * 100,
            DeviceCapability.STORAGE: self.storage_gb * 10,
            DeviceCapability.MEMORY: self.memory_gb * 50,
            DeviceCapability.DISPLAY: 100 if DeviceCapability.DISPLAY in self.capabilities else 0,
            DeviceCapability.INPUT: 80 if DeviceCapability.INPUT in self.capabilities else 0,
            DeviceCapability.NETWORK: 150,
            DeviceCapability.SENSORS: 50 if DeviceCapability.SENSORS in self.capabilities else 0,
        }
        
        return scores.get(capability, 0)


class DeviceState(BaseModel):
    """设备状态信息"""
    device_id: str
    status: DeviceStatus = DeviceStatus.ONLINE
    current_load: float = 0.0  # 0-1 表示负载程度
    available_memory_mb: int = 0
    available_storage_mb: int = 0
    network_latency_ms: int = 0
    active_connections: int = 0
    last_heartbeat: datetime = Field(default_factory=datetime.now)
    
    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat()
        }


class DeviceRegistry:
    """设备注册表，管理已知设备"""
    
    def __init__(self):
        self.devices: Dict[str, DeviceInfo] = {}
        self.device_states: Dict[str, DeviceState] = {}
    
    def register_device(self, device: DeviceInfo) -> None:
        """注册新设备"""
        self.devices[device.device_id] = device
        self.device_states[device.device_id] = DeviceState(
            device_id=device.device_id,
            status=DeviceStatus.ONLINE
        )
    
    def unregister_device(self, device_id: str) -> None:
        """注销设备"""
        if device_id in self.devices:
            del self.devices[device_id]
        if device_id in self.device_states:
            del self.device_states[device_id]
    
    def update_device_state(self, device_id: str, state: DeviceState) -> None:
        """更新设备状态"""
        if device_id in self.device_states:
            self.device_states[device_id] = state
    
    def get_device(self, device_id: str) -> Optional[DeviceInfo]:
        """获取设备信息"""
        return self.devices.get(device_id)
    
    def get_device_state(self, device_id: str) -> Optional[DeviceState]:
        """获取设备状态"""
        return self.device_states.get(device_id)
    
    def list_devices(self) -> List[DeviceInfo]:
        """列出所有设备"""
        return list(self.devices.values())
    
    def list_online_devices(self) -> List[DeviceInfo]:
        """列出在线设备"""
        online_devices = []
        for device_id, state in self.device_states.items():
            if state.status == DeviceStatus.ONLINE and device_id in self.devices:
                online_devices.append(self.devices[device_id])
        return online_devices
    
    def find_device_by_capability(self, capability: DeviceCapability) -> List[DeviceInfo]:
        """根据能力查找设备"""
        return [
            device for device in self.devices.values()
            if capability in device.capabilities
        ]
    
    def cleanup_offline_devices(self, timeout_seconds: int = 300) -> None:
        """清理离线超时的设备"""
        now = datetime.now()
        to_remove = []
        
        for device_id, state in self.device_states.items():
            time_since_heartbeat = (now - state.last_heartbeat).total_seconds()
            if time_since_heartbeat > timeout_seconds:
                to_remove.append(device_id)
        
        for device_id in to_remove:
            self.unregister_device(device_id)

path: build/lib/core/discovery.py
content: """
设备发现服务
使用 Zeroconf 实现局域网内的设备自动发现
"""

import asyncio
import socket
import logging
from typing import Dict, Any, Optional, Callable
from dataclasses import dataclass
from contextlib import asynccontextmanager

import zeroconf
from zeroconf.asyncio import AsyncZeroconf, AsyncServiceInfo, AsyncServiceBrowser

from .device import DeviceInfo, DeviceType, DeviceCapability
from .config import PAOConfig

logger = logging.getLogger(__name__)


@dataclass
class DiscoveryConfig:
    """发现服务配置"""
    service_type: str = "_pao._tcp.local."
    service_name: str = "pao-device"
    domain: str = "local."
    port: int = 8765
    scan_interval: int = 30  # 扫描间隔（秒）
    timeout: int = 10  # 发现超时（秒）
    retry_count: int = 3  # 重试次数


class DeviceDiscoveryService:
    """设备发现服务"""
    
    def __init__(
        self,
        config: PAOConfig,
        device_registry,
        on_device_discovered: Optional[Callable[[DeviceInfo], None]] = None,
        on_device_lost: Optional[Callable[[str], None]] = None
    ):
        self.config = config
        self.device_registry = device_registry
        self.on_device_discovered = on_device_discovered
        self.on_device_lost = on_device_lost
        
        self.discovery_config = DiscoveryConfig()
        self.zeroconf: Optional[AsyncZeroconf] = None
        self.service_browser: Optional[AsyncServiceBrowser] = None
        self.service_info: Optional[AsyncServiceInfo] = None
        
        # 运行状态
        self.is_running = False
        self.discovery_task: Optional[asyncio.Task] = None
        
        # 本地设备信息
        self.local_device = self._create_local_device_info()
    
    def _create_local_device_info(self) -> DeviceInfo:
        """创建本地设备信息"""
        device = DeviceInfo()
        device.name = self.config.device_name
        device.device_type = self._detect_device_type()
        device.capabilities = self._detect_capabilities()
        
        # 获取系统信息
        import platform
        import psutil
        
        device.os_name = platform.system()
        device.os_version = platform.version()
        device.cpu_cores = psutil.cpu_count(logical=False) or 1
        device.memory_gb = psutil.virtual_memory().total // (1024**3)
        
        # 存储信息
        try:
            device.storage_gb = psutil.disk_usage('/').total // (1024**3)
        except:
            device.storage_gb = 0
        
        # MAC地址
        try:
            import uuid
            device.mac_address = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
                                         for elements in range(0, 8*6, 8)][::-1])
        except:
            device.mac_address = "unknown"
        
        return device
    
    def _detect_device_type(self) -> DeviceType:
        """检测设备类型"""
        import platform
        
        system = platform.system().lower()
        machine = platform.machine().lower()
        
        if "linux" in system:
            # 可能是服务器或IoT
            if "arm" in machine or "aarch" in machine:
                return DeviceType.IOT
            else:
                return DeviceType.SERVER
        elif "windows" in system or "darwin" in system:
            # 桌面设备
            if "laptop" in platform.node().lower():
                return DeviceType.LAPTOP
            return DeviceType.DESKTOP
        elif "android" in system:
            return DeviceType.PHONE
        elif "ios" in system:
            return DeviceType.TABLET
        else:
            return DeviceType.UNKNOWN
    
    def _detect_capabilities(self) -> list:
        """检测设备能力"""
        capabilities = []
        
        # 所有设备都有计算和网络能力
        capabilities.append(DeviceCapability.COMPUTE)
        capabilities.append(DeviceCapability.NETWORK)
        
        import platform
        import psutil
        
        # 存储能力
        if psutil.disk_usage('/').total > 0:
            capabilities.append(DeviceCapability.STORAGE)
        
        # 内存能力
        if psutil.virtual_memory().total > 0:
            capabilities.append(DeviceCapability.MEMORY)
        
        # 显示能力（根据平台判断）
        system = platform.system().lower()
        if system in ['windows', 'darwin', 'linux']:
            # 尝试检测是否有显示
            try:
                import os
                if 'DISPLAY' in os.environ or system != 'linux':
                    capabilities.append(DeviceCapability.DISPLAY)
                    capabilities.append(DeviceCapability.INPUT)
            except:
                pass
        
        return capabilities
    
    async def start(self) -> None:
        """启动发现服务"""
        if self.is_running:
            logger.warning("发现服务已经在运行")
            return
        
        logger.info(f"启动设备发现服务: {self.local_device.name}")
        
        # 创建 Zeroconf 实例
        self.zeroconf = AsyncZeroconf()
        
        # 注册本地服务
        await self._register_service()
        
        # 启动服务浏览器
        await self._start_browser()
        
        # 设置运行状态
        self.is_running = True
        
        # 启动定期扫描任务
        self.discovery_task = asyncio.create_task(self._periodic_discovery())
        
        logger.info("设备发现服务启动完成")
    
    async def stop(self) -> None:
        """停止发现服务"""
        if not self.is_running:
            return
        
        logger.info("停止设备发现服务")
        
        # 取消定期任务
        if self.discovery_task and not self.discovery_task.done():
            self.discovery_task.cancel()
            try:
                await self.discovery_task
            except asyncio.CancelledError:
                pass
        
        # 取消服务注册
        if self.service_info and self.zeroconf:
            await self.zeroconf.async_unregister_service(self.service_info)
        
        # 关闭浏览器
        if self.service_browser:
            self.service_browser.cancel()
        
        # 关闭 Zeroconf
        if self.zeroconf:
            await self.zeroconf.async_close()
        
        self.is_running = False
        logger.info("设备发现服务已停止")
    
    async def _register_service(self) -> None:
        """注册本地设备服务"""
        service_type = self.discovery_config.service_type
        service_name = f"{self.local_device.name}.{service_type}"
        
        # 服务属性
        properties = self.local_device.to_dict()
        
        # 创建服务信息
        self.service_info = AsyncServiceInfo(
            service_type,
            service_name,
            addresses=[socket.inet_aton(self.local_device.ip_address)],
            port=self.discovery_config.port,
            properties=properties,
            server=f"{self.local_device.hostname}.local."
        )
        
        # 注册服务
        if self.zeroconf:
            await self.zeroconf.async_register_service(self.service_info)
            logger.info(f"已注册服务: {service_name}")
    
    async def _start_browser(self) -> None:
        """启动服务浏览器"""
        if not self.zeroconf:
            return
        
        # 创建浏览器回调
        listener = self._create_service_listener()
        
        # 启动浏览器
        self.service_browser = AsyncServiceBrowser(
            self.zeroconf.zeroconf,
            self.discovery_config.service_type,
            listener
        )
        
        logger.info("服务浏览器已启动")
    
    def _create_service_listener(self):
        """创建服务监听器"""
        class ServiceListener:
            def __init__(self, discovery_service):
                self.discovery_service = discovery_service
            
            def add_service(self, zeroconf, service_type, name):
                asyncio.create_task(
                    self.discovery_service._on_service_added(zeroconf, service_type, name)
                )
            
            def remove_service(self, zeroconf, service_type, name):
                asyncio.create_task(
                    self.discovery_service._on_service_removed(zeroconf, service_type, name)
                )
            
            def update_service(self, zeroconf, service_type, name):
                asyncio.create_task(
                    self.discovery_service._on_service_updated(zeroconf, service_type, name)
                )
        
        return ServiceListener(self)
    
    async def _on_service_added(self, zeroconf, service_type, name) -> None:
        """处理服务添加事件"""
        logger.info(f"发现新服务: {name}")
        
        # 获取服务信息
        info = await AsyncServiceInfo.async_from_zeroconf(zeroconf, service_type, name)
        if info:
            await self._process_service_info(info)
    
    async def _on_service_removed(self, zeroconf, service_type, name) -> None:
        """处理服务移除事件"""
        logger.info(f"服务移除: {name}")
        
        # 从设备ID中提取设备ID
        device_id = name.split('.')[0]
        
        # 通知设备丢失
        if self.on_device_lost:
            self.on_device_lost(device_id)
    
    async def _on_service_updated(self, zeroconf, service_type, name) -> None:
        """处理服务更新事件"""
        logger.debug(f"服务更新: {name}")
        
        # 重新获取服务信息
        info = await AsyncServiceInfo.async_from_zeroconf(zeroconf, service_type, name)
        if info:
            await self._process_service_info(info)
    
    async def _process_service_info(self, info: AsyncServiceInfo) -> None:
        """处理服务信息"""
        try:
            # 解析设备信息
            properties = info.properties or {}
            
            # 转换为设备信息
            device_info = DeviceInfo.from_dict({
                **properties,
                "ip_address": socket.inet_ntoa(info.addresses[0]) if info.addresses else "0.0.0.0",
                "port": info.port,
                "hostname": info.server.rstrip('.local.')
            })
            
            # 更新最后出现时间
            device_info.update_last_seen()
            
            # 注册设备
            self.device_registry.register_device(device_info)
            
            logger.info(f"发现设备: {device_info.name} ({device_info.device_type.value})")
            
            # 通知回调
            if self.on_device_discovered:
                self.on_device_discovered(device_info)
                
        except Exception as e:
            logger.error(f"处理服务信息失败: {e}")
    
    async def _periodic_discovery(self) -> None:
        """定期发现任务"""
        while self.is_running:
            try:
                await asyncio.sleep(self.discovery_config.scan_interval)
                
                # 主动扫描
                await self._active_scan()
                
                # 清理离线设备
                self.device_registry.cleanup_offline_devices()
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"定期发现任务出错: {e}")
    
    async def _active_scan(self) -> None:
        """主动扫描网络中的设备"""
        if not self.zeroconf:
            return
        
        # 使用 Zeroconf 主动查询服务
        try:
            services = await self.zeroconf.async_get_service_info(
                self.discovery_config.service_type,
                f"{self.local_device.name}.{self.discovery_config.service_type}"
            )
            
            if services:
                await self._process_service_info(services)
                
        except Exception as e:
            logger.debug(f"主动扫描出错: {e}")
    
    async def discover_devices(self, timeout: Optional[int] = None) -> list:
        """主动发现设备（阻塞方式）"""
        if timeout is None:
            timeout = self.discovery_config.timeout
        
        # 记录当前设备数量
        initial_count = len(self.device_registry.list_devices())
        
        # 等待一段时间
        await asyncio.sleep(timeout)
        
        # 返回新发现的设备
        current_count = len(self.device_registry.list_devices())
        new_devices = current_count - initial_count
        
        logger.info(f"发现 {new_devices} 个新设备")
        return self.device_registry.list_devices()


@asynccontextmanager
async def discovery_service(config: PAOConfig, device_registry):
    """发现服务的上下文管理器"""
    service = DeviceDiscoveryService(config, device_registry)
    try:
        await service.start()
        yield service
    finally:
        await service.stop()


async def demo_discovery():
    """演示设备发现功能"""
    from .config import load_config
    
    config = load_config()
    from .device import DeviceRegistry
    
    device_registry = DeviceRegistry()
    
    # 创建设备发现服务
    async with discovery_service(config, device_registry) as service:
        print(f"本地设备: {service.local_device.name}")
        print("开始发现设备...")
        
        # 等待10秒发现设备
        devices = await service.discover_devices(timeout=10)
        
        print(f"\n发现 {len(devices)} 个设备:")
        for device in devices:
            print(f"  - {device.name} ({device.device_type.value}) "
                  f"[{device.ip_address}:{device.port}]")
        
        # 保持运行
        print("\n按 Ctrl+C 停止...")
        try:
            while True:
                await asyncio.sleep(1)
        except KeyboardInterrupt:
            print("停止发现服务")


if __name__ == "__main__":
    import asyncio
    asyncio.run(demo_discovery())

path: build/lib/core/memory.py
content: """
本地记忆系统
实现 AI 记忆的存储、检索和管理
"""

import json
import time
import uuid
from datetime import datetime, timezone
from enum import Enum
from typing import Dict, List, Optional, Any, Union
from dataclasses import dataclass, asdict, field
from pathlib import Path

import aiofiles
from pydantic import BaseModel, Field


class MemoryType(str, Enum):
    """记忆类型"""
    CONVERSATION = "conversation"      # 对话记忆
    KNOWLEDGE = "knowledge"            # 知识记忆
    TASK = "task"                      # 任务记忆
    PREFERENCE = "preference"          # 偏好记忆
    CONTEXT = "context"                # 上下文记忆
    SYSTEM = "system"                  # 系统记忆


class MemoryPriority(int, Enum):
    """记忆优先级"""
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4


@dataclass
class MemoryMetadata:
    """记忆元数据"""
    source: str = ""                    # 来源（设备ID或用户ID）
    confidence: float = 1.0             # 置信度（0.0-1.0）
    access_count: int = 0               # 访问次数
    last_accessed: float = 0.0          # 最后访问时间戳
    created_at: float = field(default_factory=time.time)  # 创建时间戳
    tags: List[str] = field(default_factory=list)         # 标签
    expires_at: Optional[float] = None  # 过期时间戳（None表示永不过期）


class MemoryItem(BaseModel):
    """记忆项"""
    
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    type: MemoryType
    content: Dict[str, Any]
    priority: MemoryPriority = MemoryPriority.MEDIUM
    metadata: MemoryMetadata = Field(default_factory=MemoryMetadata)
    
    # 时间戳
    created_at: float = Field(default_factory=time.time)
    updated_at: float = Field(default_factory=time.time)
    
    # 关联信息
    related_memories: List[str] = Field(default_factory=list)  # 关联记忆ID
    device_id: Optional[str] = None  # 创建此记忆的设备ID
    
    class Config:
        use_enum_values = True


class MemoryQuery(BaseModel):
    """记忆查询条件"""
    
    type: Optional[MemoryType] = None
    content_key: Optional[str] = None
    content_value: Optional[Any] = None
    min_priority: Optional[MemoryPriority] = None
    tags: Optional[List[str]] = None
    source: Optional[str] = None
    device_id: Optional[str] = None
    created_after: Optional[float] = None
    created_before: Optional[float] = None
    limit: int = 100
    offset: int = 0


class MemorySystem:
    """记忆系统"""
    
    def __init__(self, storage_path: Optional[Path] = None):
        """
        初始化记忆系统
        
        Args:
            storage_path: 存储路径，如果为None则使用默认路径
        """
        if storage_path is None:
            storage_path = Path.home() / ".pao" / "memories"
        
        self.storage_path = Path(storage_path)
        self.memories: Dict[str, MemoryItem] = {}
        
        # 创建存储目录
        self.storage_path.mkdir(parents=True, exist_ok=True)
        
        # 索引
        self.type_index: Dict[MemoryType, List[str]] = {}
        self.tag_index: Dict[str, List[str]] = {}
        self.device_index: Dict[str, List[str]] = {}
        
    async def load(self) -> None:
        """从存储加载所有记忆"""
        try:
            memory_files = list(self.storage_path.glob("*.json"))
            
            for file_path in memory_files:
                try:
                    async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
                        content = await f.read()
                    
                    data = json.loads(content)
                    memory = MemoryItem(**data)
                    
                    # 添加到内存
                    self.memories[memory.id] = memory
                    
                    # 更新索引
                    self._add_to_indices(memory)
                    
                except Exception as e:
                    print(f"加载记忆文件 {file_path} 失败: {e}")
        
        except Exception as e:
            print(f"加载记忆系统失败: {e}")
    
    async def save(self, memory: MemoryItem) -> None:
        """
        保存记忆
        
        Args:
            memory: 要保存的记忆
        """
        try:
            # 更新时间戳
            memory.updated_at = time.time()
            
            # 保存到内存
            self.memories[memory.id] = memory
            
            # 更新索引
            self._add_to_indices(memory)
            
            # 保存到文件
            file_path = self.storage_path / f"{memory.id}.json"
            
            async with aiofiles.open(file_path, "w", encoding="utf-8") as f:
                await f.write(memory.model_dump_json(indent=2))

            # 更新访问统计
            memory.metadata.access_count += 1
            memory.metadata.last_accessed = time.time()
            
        except Exception as e:
            print(f"保存记忆失败: {e}")
            raise
    
    async def get(self, memory_id: str) -> Optional[MemoryItem]:
        """
        获取记忆
        
        Args:
            memory_id: 记忆ID
            
        Returns:
            记忆项，如果不存在则返回None
        """
        memory = self.memories.get(memory_id)
        
        if memory:
            # 更新访问统计
            memory.metadata.access_count += 1
            memory.metadata.last_accessed = time.time()
            
            # 异步保存更新
            asyncio.create_task(self._update_memory_file(memory))
        
        return memory
    
    async def query(self, query: MemoryQuery) -> List[MemoryItem]:
        """
        查询记忆
        
        Args:
            query: 查询条件
            
        Returns:
            匹配的记忆列表
        """
        results = []
        
        for memory in self.memories.values():
            if self._matches_query(memory, query):
                results.append(memory)
        
        # 按创建时间排序（最新的在前面）
        results.sort(key=lambda m: m.created_at, reverse=True)
        
        # 应用分页
        start = query.offset
        end = start + query.limit
        return results[start:end]
    
    async def delete(self, memory_id: str) -> bool:
        """
        删除记忆
        
        Args:
            memory_id: 记忆ID
            
        Returns:
            是否成功删除
        """
        try:
            memory = self.memories.get(memory_id)
            if not memory:
                return False
            
            # 从内存中删除
            del self.memories[memory_id]
            
            # 从索引中删除
            self._remove_from_indices(memory)
            
            # 删除文件
            file_path = self.storage_path / f"{memory_id}.json"
            if file_path.exists():
                file_path.unlink()
            
            return True
        
        except Exception as e:
            print(f"删除记忆失败: {e}")
            return False
    
    async def update(self, memory_id: str, updates: Dict[str, Any]) -> Optional[MemoryItem]:
        """
        更新记忆
        
        Args:
            memory_id: 记忆ID
            updates: 更新字段
            
        Returns:
            更新后的记忆，如果不存在则返回None
        """
        memory = await self.get(memory_id)
        if not memory:
            return None
        
        # 应用更新
        for key, value in updates.items():
            if hasattr(memory, key):
                setattr(memory, key, value)
        
        memory.updated_at = time.time()
        
        # 保存更新
        await self.save(memory)
        
        return memory
    
    async def cleanup_expired(self) -> int:
        """
        清理过期记忆
        
        Returns:
            清理的记忆数量
        """
        current_time = time.time()
        expired_ids = []
        
        for memory_id, memory in self.memories.items():
            if memory.metadata.expires_at and memory.metadata.expires_at < current_time:
                expired_ids.append(memory_id)
        
        # 删除过期记忆
        deleted_count = 0
        for memory_id in expired_ids:
            if await self.delete(memory_id):
                deleted_count += 1
        
        return deleted_count
    
    def get_stats(self) -> Dict[str, Any]:
        """
        获取统计信息
        
        Returns:
            统计信息字典
        """
        return {
            "total_memories": len(self.memories),
            "by_type": {
                type_.value: len(memories) 
                for type_, memories in self.type_index.items()
            },
            "by_device": {
                device_id: len(memories)
                for device_id, memories in self.device_index.items()
            },
            "oldest_memory": min(
                [m.created_at for m in self.memories.values()] or [0]
            ),
            "newest_memory": max(
                [m.created_at for m in self.memories.values()] or [0]
            )
        }

    async def add_memory(self, memory_type: str, content: Any, priority: int = 2) -> str:
        """添加记忆"""
        mem_type = MemoryType.CONVERSATION
        for mt in MemoryType:
            if mt.value == memory_type:
                mem_type = mt
                break
        # 确保priority是有效的枚举值
        valid_priority = max(1, min(4, priority))
        memory = MemoryItem(
            id=str(uuid.uuid4()),
            type=mem_type,
            content=content,
            priority=valid_priority,
            metadata=MemoryMetadata()
        )
        await self.save(memory)
        return memory.id

    async def search_memories(self, query: str, limit: int = 10) -> list:
        """搜索记忆"""
        memory_query = MemoryQuery(keyword=query, limit=limit)
        results = await self.query(memory_query)
        return [m.content for m in results]

    def _add_to_indices(self, memory: MemoryItem) -> None:
        """添加到索引"""
        # 类型索引
        if memory.type not in self.type_index:
            self.type_index[memory.type] = []
        if memory.id not in self.type_index[memory.type]:
            self.type_index[memory.type].append(memory.id)
        
        # 标签索引
        for tag in memory.metadata.tags:
            if tag not in self.tag_index:
                self.tag_index[tag] = []
            if memory.id not in self.tag_index[tag]:
                self.tag_index[tag].append(memory.id)
        
        # 设备索引
        if memory.device_id:
            if memory.device_id not in self.device_index:
                self.device_index[memory.device_id] = []
            if memory.id not in self.device_index[memory.device_id]:
                self.device_index[memory.device_id].append(memory.id)
    
    def _remove_from_indices(self, memory: MemoryItem) -> None:
        """从索引中删除"""
        # 类型索引
        if memory.type in self.type_index:
            if memory.id in self.type_index[memory.type]:
                self.type_index[memory.type].remove(memory.id)
        
        # 标签索引
        for tag in memory.metadata.tags:
            if tag in self.tag_index and memory.id in self.tag_index[tag]:
                self.tag_index[tag].remove(memory.id)
        
        # 设备索引
        if memory.device_id and memory.device_id in self.device_index:
            if memory.id in self.device_index[memory.device_id]:
                self.device_index[memory.device_id].remove(memory.id)
    
    def _matches_query(self, memory: MemoryItem, query: MemoryQuery) -> bool:
        """检查记忆是否匹配查询条件"""
        if query.type and memory.type != query.type:
            return False
        
        if query.content_key and query.content_value:
            if query.content_key not in memory.content:
                return False
            if memory.content[query.content_key] != query.content_value:
                return False
        
        if query.min_priority and memory.priority.value < query.min_priority.value:
            return False
        
        if query.tags:
            if not all(tag in memory.metadata.tags for tag in query.tags):
                return False
        
        if query.source and memory.metadata.source != query.source:
            return False
        
        if query.device_id and memory.device_id != query.device_id:
            return False
        
        if query.created_after and memory.created_at < query.created_after:
            return False
        
        if query.created_before and memory.created_at > query.created_before:
            return False
        
        return True
    
    async def _update_memory_file(self, memory: MemoryItem) -> None:
        """异步更新记忆文件"""
        try:
            file_path = self.storage_path / f"{memory.id}.json"
            async with aiofiles.open(file_path, "w", encoding="utf-8") as f:
                await f.write(memory.model_dump_json(indent=2))
        except Exception as e:
            print(f"异步更新记忆文件失败: {e}")


import asyncio

path: build/lib/core/storage.py
content: """
数据存储系统
提供统一的存储接口，支持多种存储后端
"""

import json
import sqlite3
import aiofiles
from pathlib import Path
from typing import Dict, List, Optional, Any, Union, Tuple
from enum import Enum
import asyncio
from dataclasses import dataclass, asdict
from datetime import datetime
import time


class StorageType(str, Enum):
    """存储类型"""
    SQLITE = "sqlite"
    JSON_FILE = "json"
    MEMORY = "memory"


@dataclass
class StorageConfig:
    """存储配置"""
    
    type: StorageType = StorageType.SQLITE
    path: Optional[Path] = None
    table_name: str = "memories"
    max_connections: int = 5
    auto_commit: bool = True


class StorageBackend:
    """存储后端基类"""
    
    async def connect(self) -> None:
        """连接到存储"""
        pass
    
    async def disconnect(self) -> None:
        """断开连接"""
        pass
    
    async def save(self, key: str, data: Dict[str, Any]) -> bool:
        """保存数据"""
        raise NotImplementedError
    
    async def load(self, key: str) -> Optional[Dict[str, Any]]:
        """加载数据"""
        raise NotImplementedError
    
    async def delete(self, key: str) -> bool:
        """删除数据"""
        raise NotImplementedError
    
    async def query(self, conditions: Dict[str, Any], limit: int = 100) -> List[Dict[str, Any]]:
        """查询数据"""
        raise NotImplementedError
    
    async def exists(self, key: str) -> bool:
        """检查数据是否存在"""
        raise NotImplementedError
    
    async def list_keys(self, prefix: str = "") -> List[str]:
        """列出所有键"""
        raise NotImplementedError


class SQLiteBackend(StorageBackend):
    """SQLite 存储后端"""
    
    def __init__(self, config: StorageConfig):
        self.config = config
        self.connection: Optional[sqlite3.Connection] = None
        
        # 确保路径存在
        if self.config.path:
            path = self.config.path if isinstance(self.config.path, Path) else Path(self.config.path)
            path.parent.mkdir(parents=True, exist_ok=True)
    
    async def connect(self) -> None:
        """连接到 SQLite 数据库"""
        if self.config.path is None:
            raise ValueError("SQLite 存储需要指定路径")
        
        # SQLite 连接是同步的，但我们在异步环境中使用
        self.connection = sqlite3.connect(
            str(self.config.path),
            check_same_thread=False
        )
        
        # 启用外键和 WAL 模式
        self.connection.execute("PRAGMA foreign_keys = ON")
        self.connection.execute("PRAGMA journal_mode = WAL")
        
        # 创建表
        await self._create_tables()
    
    async def disconnect(self) -> None:
        """断开连接"""
        if self.connection:
            self.connection.close()
            self.connection = None
    
    async def save(self, key: str, data: Dict[str, Any]) -> bool:
        """保存数据到 SQLite"""
        if not self.connection:
            await self.connect()
        
        try:
            # 序列化数据
            data_json = json.dumps(data, ensure_ascii=False)
            
            # 插入或更新
            cursor = self.connection.execute(
                f"""
                INSERT OR REPLACE INTO {self.config.table_name} 
                (key, data, created_at, updated_at) 
                VALUES (?, ?, ?, ?)
                """,
                (key, data_json, time.time(), time.time())
            )
            
            if self.config.auto_commit:
                self.connection.commit()
            
            return cursor.rowcount > 0
        
        except Exception as e:
            print(f"SQLite 保存失败: {e}")
            return False
    
    async def load(self, key: str) -> Optional[Dict[str, Any]]:
        """从 SQLite 加载数据"""
        if not self.connection:
            await self.connect()
        
        try:
            cursor = self.connection.execute(
                f"SELECT data FROM {self.config.table_name} WHERE key = ?",
                (key,)
            )
            
            row = cursor.fetchone()
            if row:
                return json.loads(row[0])
            
            return None
        
        except Exception as e:
            print(f"SQLite 加载失败: {e}")
            return None
    
    async def delete(self, key: str) -> bool:
        """从 SQLite 删除数据"""
        if not self.connection:
            await self.connect()
        
        try:
            cursor = self.connection.execute(
                f"DELETE FROM {self.config.table_name} WHERE key = ?",
                (key,)
            )
            
            if self.config.auto_commit:
                self.connection.commit()
            
            return cursor.rowcount > 0
        
        except Exception as e:
            print(f"SQLite 删除失败: {e}")
            return False
    
    async def query(self, conditions: Dict[str, Any], limit: int = 100) -> List[Dict[str, Any]]:
        """查询 SQLite 数据"""
        if not self.connection:
            await self.connect()
        
        try:
            # 构建查询条件
            where_clauses = []
            params = []
            
            for key, value in conditions.items():
                where_clauses.append(f"json_extract(data, '$.{key}') = ?")
                params.append(value)
            
            where_sql = " AND ".join(where_clauses) if where_clauses else "1=1"
            
            # 执行查询
            cursor = self.connection.execute(
                f"""
                SELECT data FROM {self.config.table_name} 
                WHERE {where_sql} 
                ORDER BY updated_at DESC 
                LIMIT ?
                """,
                (*params, limit)
            )
            
            results = []
            for row in cursor.fetchall():
                results.append(json.loads(row[0]))
            
            return results
        
        except Exception as e:
            print(f"SQLite 查询失败: {e}")
            return []
    
    async def exists(self, key: str) -> bool:
        """检查键是否存在"""
        if not self.connection:
            await self.connect()
        
        try:
            cursor = self.connection.execute(
                f"SELECT 1 FROM {self.config.table_name} WHERE key = ?",
                (key,)
            )
            
            return cursor.fetchone() is not None
        
        except Exception as e:
            print(f"SQLite 检查存在失败: {e}")
            return False
    
    async def list_keys(self, prefix: str = "") -> List[str]:
        """列出所有键"""
        if not self.connection:
            await self.connect()
        
        try:
            if prefix:
                cursor = self.connection.execute(
                    f"SELECT key FROM {self.config.table_name} WHERE key LIKE ?",
                    (f"{prefix}%",)
                )
            else:
                cursor = self.connection.execute(
                    f"SELECT key FROM {self.config.table_name}"
                )
            
            return [row[0] for row in cursor.fetchall()]
        
        except Exception as e:
            print(f"SQLite 列出键失败: {e}")
            return []
    
    async def _create_tables(self) -> None:
        """创建数据库表"""
        if not self.connection:
            return
        
        self.connection.execute(f"""
            CREATE TABLE IF NOT EXISTS {self.config.table_name} (
                key TEXT PRIMARY KEY,
                data TEXT NOT NULL,
                created_at REAL NOT NULL,
                updated_at REAL NOT NULL
            )
        """)
        
        # 创建索引
        self.connection.execute(f"""
            CREATE INDEX IF NOT EXISTS idx_updated_at 
            ON {self.config.table_name} (updated_at)
        """)
        
        self.connection.commit()


class JSONFileBackend(StorageBackend):
    """JSON 文件存储后端"""
    
    def __init__(self, config: StorageConfig):
        self.config = config
        self.data: Dict[str, Dict[str, Any]] = {}
        
        # 确保路径存在
        if self.config.path:
            path = self.config.path if isinstance(self.config.path, Path) else Path(self.config.path)
            path.parent.mkdir(parents=True, exist_ok=True)
    
    async def connect(self) -> None:
        """连接到 JSON 文件存储"""
        if self.config.path and self.config.path.exists():
            await self._load_from_file()
    
    async def disconnect(self) -> None:
        """断开连接并保存到文件"""
        if self.config.path:
            await self._save_to_file()
    
    async def save(self, key: str, data: Dict[str, Any]) -> bool:
        """保存数据到 JSON 文件"""
        try:
            # 添加时间戳
            if "created_at" not in data:
                data["created_at"] = time.time()
            data["updated_at"] = time.time()
            
            # 保存到内存
            self.data[key] = data
            
            # 异步保存到文件
            asyncio.create_task(self._save_to_file())
            
            return True
        
        except Exception as e:
            print(f"JSON 文件保存失败: {e}")
            return False
    
    async def load(self, key: str) -> Optional[Dict[str, Any]]:
        """从 JSON 文件加载数据"""
        return self.data.get(key)
    
    async def delete(self, key: str) -> bool:
        """从 JSON 文件删除数据"""
        try:
            if key in self.data:
                del self.data[key]
                
                # 异步保存到文件
                asyncio.create_task(self._save_to_file())
                
                return True
            
            return False
        
        except Exception as e:
            print(f"JSON 文件删除失败: {e}")
            return False
    
    async def query(self, conditions: Dict[str, Any], limit: int = 100) -> List[Dict[str, Any]]:
        """查询 JSON 文件数据"""
        results = []
        
        for item in self.data.values():
            match = True
            
            for key, value in conditions.items():
                if key not in item or item[key] != value:
                    match = False
                    break
            
            if match:
                results.append(item)
        
        # 按更新时间排序
        results.sort(key=lambda x: x.get("updated_at", 0), reverse=True)
        
        return results[:limit]
    
    async def exists(self, key: str) -> bool:
        """检查键是否存在"""
        return key in self.data
    
    async def list_keys(self, prefix: str = "") -> List[str]:
        """列出所有键"""
        if prefix:
            return [key for key in self.data.keys() if key.startswith(prefix)]
        
        return list(self.data.keys())
    
    async def _load_from_file(self) -> None:
        """从文件加载数据"""
        if not self.config.path or not self.config.path.exists():
            return
        
        try:
            async with aiofiles.open(self.config.path, "r", encoding="utf-8") as f:
                content = await f.read()
                self.data = json.loads(content)
        
        except Exception as e:
            print(f"加载 JSON 文件失败: {e}")
            self.data = {}
    
    async def _save_to_file(self) -> None:
        """保存数据到文件"""
        if not self.config.path:
            return
        
        try:
            async with aiofiles.open(self.config.path, "w", encoding="utf-8") as f:
                await f.write(json.dumps(self.data, indent=2, ensure_ascii=False))
        
        except Exception as e:
            print(f"保存 JSON 文件失败: {e}")


class MemoryBackend(StorageBackend):
    """内存存储后端（用于测试）"""
    
    def __init__(self, config: StorageConfig):
        self.config = config
        self.data: Dict[str, Dict[str, Any]] = {}
    
    async def save(self, key: str, data: Dict[str, Any]) -> bool:
        """保存数据到内存"""
        try:
            if "created_at" not in data:
                data["created_at"] = time.time()
            data["updated_at"] = time.time()
            
            self.data[key] = data
            return True
        
        except Exception as e:
            print(f"内存保存失败: {e}")
            return False
    
    async def load(self, key: str) -> Optional[Dict[str, Any]]:
        """从内存加载数据"""
        return self.data.get(key)
    
    async def delete(self, key: str) -> bool:
        """从内存删除数据"""
        try:
            if key in self.data:
                del self.data[key]
                return True
            
            return False
        
        except Exception as e:
            print(f"内存删除失败: {e}")
            return False
    
    async def query(self, conditions: Dict[str, Any], limit: int = 100) -> List[Dict[str, Any]]:
        """查询内存数据"""
        results = []
        
        for item in self.data.values():
            match = True
            
            for key, value in conditions.items():
                if key not in item or item[key] != value:
                    match = False
                    break
            
            if match:
                results.append(item)
        
        # 按更新时间排序
        results.sort(key=lambda x: x.get("updated_at", 0), reverse=True)
        
        return results[:limit]
    
    async def exists(self, key: str) -> bool:
        """检查键是否存在"""
        return key in self.data
    
    async def list_keys(self, prefix: str = "") -> List[str]:
        """列出所有键"""
        if prefix:
            return [key for key in self.data.keys() if key.startswith(prefix)]
        
        return list(self.data.keys())


class StorageManager:
    """存储管理器"""
    
    def __init__(self, config: Optional[StorageConfig] = None):
        self.config = config or StorageConfig()
        self.backend: Optional[StorageBackend] = None
        
    async def initialize(self) -> None:
        """初始化存储"""
        # 根据配置创建后端
        if self.config.type == StorageType.SQLITE:
            self.backend = SQLiteBackend(self.config)
        
        elif self.config.type == StorageType.JSON_FILE:
            self.backend = JSONFileBackend(self.config)
        
        elif self.config.type == StorageType.MEMORY:
            self.backend = MemoryBackend(self.config)
        
        else:
            raise ValueError(f"不支持的存储类型: {self.config.type}")
        
        # 连接后端
        await self.backend.connect()
    
    async def close(self) -> None:
        """关闭存储"""
        if self.backend:
            await self.backend.disconnect()
    
    async def save(self, key: str, data: Dict[str, Any]) -> bool:
        """保存数据"""
        if not self.backend:
            await self.initialize()
        
        return await self.backend.save(key, data)
    
    async def load(self, key: str) -> Optional[Dict[str, Any]]:
        """加载数据"""
        if not self.backend:
            await self.initialize()
        
        return await self.backend.load(key)
    
    async def delete(self, key: str) -> bool:
        """删除数据"""
        if not self.backend:
            await self.initialize()
        
        return await self.backend.delete(key)
    
    async def query(self, conditions: Dict[str, Any], limit: int = 100) -> List[Dict[str, Any]]:
        """查询数据"""
        if not self.backend:
            await self.initialize()
        
        return await self.backend.query(conditions, limit)
    
    async def exists(self, key: str) -> bool:
        """检查数据是否存在"""
        if not self.backend:
            await self.initialize()
        
        return await self.backend.exists(key)
    
    async def list_keys(self, prefix: str = "") -> List[str]:
        """列出所有键"""
        if not self.backend:
            await self.initialize()
        
        return await self.backend.list_keys(prefix)
    
    async def migrate_data(self, source_backend: StorageBackend, target_backend: StorageBackend) -> int:
        """
        迁移数据
        
        Args:
            source_backend: 源存储后端
            target_backend: 目标存储后端
            
        Returns:
            迁移的数据数量
        """
        migrated_count = 0
        
        try:
            # 获取所有键
            keys = await source_backend.list_keys()
            
            for key in keys:
                # 加载数据
                data = await source_backend.load(key)
                if data:
                    # 保存到目标
                    if await target_backend.save(key, data):
                        migrated_count += 1
            
            return migrated_count
        
        except Exception as e:
            print(f"数据迁移失败: {e}")
            return migrated_count

path: build/lib/core/sync.py
content: """
记忆同步系统
实现跨设备的记忆同步和冲突解决
"""

import json
import time
import hashlib
from typing import Dict, List, Optional, Any, Set, Tuple
from dataclasses import dataclass, field
from enum import Enum
import asyncio
from datetime import datetime, timedelta

from .memory import MemorySystem, MemoryItem, MemoryQuery, MemoryType
from .device import DeviceInfo


class SyncStatus(str, Enum):
    """同步状态"""
    PENDING = "pending"        # 等待同步
    SYNCING = "syncing"        # 同步中
    COMPLETED = "completed"    # 同步完成
    FAILED = "failed"          # 同步失败
    CONFLICT = "conflict"      # 冲突需要解决


class SyncDirection(str, Enum):
    """同步方向"""
    PUSH = "push"              # 推送（本地→远程）
    PULL = "pull"              # 拉取（远程→本地）
    BIDIRECTIONAL = "bidirectional"  # 双向同步


class SyncStrategy(str, Enum):
    """同步策略"""
    LAST_WRITE_WINS = "last_write_wins"    # 最后写入获胜
    MANUAL_MERGE = "manual_merge"          # 手动合并
    AUTO_MERGE = "auto_merge"              # 自动合并
    SOURCE_PRIORITY = "source_priority"    # 源设备优先


@dataclass
class SyncOperation:
    """同步操作"""
    
    id: str
    memory_id: str
    device_id: str
    direction: SyncDirection
    status: SyncStatus = SyncStatus.PENDING
    timestamp: float = field(default_factory=time.time)
    retry_count: int = 0
    error_message: Optional[str] = None
    
    # 冲突相关
    conflict_resolution: Optional[Dict[str, Any]] = None
    merged_memory_id: Optional[str] = None


@dataclass
class SyncSession:
    """同步会话"""
    
    id: str
    source_device_id: str
    target_device_id: str
    start_time: float = field(default_factory=time.time)
    end_time: Optional[float] = None
    status: SyncStatus = SyncStatus.PENDING
    strategy: SyncStrategy = SyncStrategy.LAST_WRITE_WINS
    
    # 同步统计
    total_memories: int = 0
    synced_memories: int = 0
    failed_memories: int = 0
    conflict_memories: int = 0
    
    # 操作列表
    operations: List[SyncOperation] = field(default_factory=list)


class ConflictResolver:
    """冲突解决器"""
    
    @staticmethod
    def resolve_last_write_wins(local_memory: MemoryItem, remote_memory: MemoryItem) -> MemoryItem:
        """最后写入获胜策略"""
        if local_memory.updated_at > remote_memory.updated_at:
            return local_memory
        else:
            return remote_memory
    
    @staticmethod
    def resolve_source_priority(local_memory: MemoryItem, remote_memory: MemoryItem, source_device_id: str) -> MemoryItem:
        """源设备优先策略"""
        if local_memory.device_id == source_device_id:
            return local_memory
        elif remote_memory.device_id == source_device_id:
            return remote_memory
        else:
            # 回退到最后写入获胜
            return ConflictResolver.resolve_last_write_wins(local_memory, remote_memory)
    
    @staticmethod
    def auto_merge(local_memory: MemoryItem, remote_memory: MemoryItem) -> MemoryItem:
        """自动合并策略"""
        # 创建合并后的记忆
        merged = MemoryItem(
            id=local_memory.id,
            type=local_memory.type,
            content=local_memory.content.copy(),
            priority=max(local_memory.priority, remote_memory.priority),
            metadata=local_memory.metadata,
            device_id=local_memory.device_id,  # 保持本地设备ID
            related_memories=list(set(local_memory.related_memories + remote_memory.related_memories))
        )
        
        # 合并内容（简单合并策略）
        for key, value in remote_memory.content.items():
            if key not in merged.content:
                merged.content[key] = value
            elif isinstance(value, dict) and isinstance(merged.content[key], dict):
                # 递归合并字典
                merged.content[key] = {**merged.content[key], **value}
            elif isinstance(value, list) and isinstance(merged.content[key], list):
                # 合并列表（去重）
                merged.content[key] = list(set(merged.content[key] + value))
        
        # 合并元数据
        merged.metadata.access_count = max(
            local_memory.metadata.access_count,
            remote_memory.metadata.access_count
        )
        merged.metadata.last_accessed = max(
            local_memory.metadata.last_accessed,
            remote_memory.metadata.last_accessed
        )
        
        # 合并标签
        merged.metadata.tags = list(set(local_memory.metadata.tags + remote_memory.metadata.tags))
        
        # 更新最后访问时间
        merged.metadata.last_accessed = time.time()
        merged.updated_at = time.time()
        
        return merged
    
    @staticmethod
    def detect_conflict(local_memory: MemoryItem, remote_memory: MemoryItem) -> bool:
        """检测冲突"""
        if local_memory.type != remote_memory.type:
            return True
        
        # 检查内容是否相同
        if local_memory.content != remote_memory.content:
            return True
        
        # 检查优先级是否相同
        if local_memory.priority != remote_memory.priority:
            return True
        
        return False


class SyncEngine:
    """同步引擎"""
    
    def __init__(self, memory_system: MemorySystem, local_device_id: str):
        self.memory_system = memory_system
        self.local_device_id = local_device_id
        
        # 同步会话管理
        self.sessions: Dict[str, SyncSession] = {}
        self.pending_operations: Dict[str, SyncOperation] = {}
        
        # 同步配置
        self.sync_interval: int = 300  # 同步间隔（秒）
        self.max_retries: int = 3
        self.batch_size: int = 50
        
        # 设备连接管理
        self.connected_devices: Dict[str, DeviceInfo] = {}
        
        # 运行状态
        self.is_running = False
        self.sync_task: Optional[asyncio.Task] = None
    
    async def start(self) -> None:
        """启动同步引擎"""
        if self.is_running:
            return
        
        self.is_running = True
        
        # 启动定期同步任务
        self.sync_task = asyncio.create_task(self._periodic_sync())
    
    async def stop(self) -> None:
        """停止同步引擎"""
        if not self.is_running:
            return
        
        self.is_running = False
        
        # 取消同步任务
        if self.sync_task:
            self.sync_task.cancel()
            try:
                await self.sync_task
            except asyncio.CancelledError:
                pass
        
        # 等待所有同步会话完成
        await self._wait_for_sessions()
    
    async def sync_with_device(self, device_id: str, strategy: SyncStrategy = SyncStrategy.LAST_WRITE_WINS) -> SyncSession:
        """
        与指定设备同步
        
        Args:
            device_id: 设备ID
            strategy: 同步策略
            
        Returns:
            同步会话
        """
        if device_id not in self.connected_devices:
            raise ValueError(f"设备未连接: {device_id}")
        
        # 创建同步会话
        session_id = hashlib.md5(f"{self.local_device_id}_{device_id}_{time.time()}".encode()).hexdigest()[:12]
        session = SyncSession(
            id=session_id,
            source_device_id=self.local_device_id,
            target_device_id=device_id,
            strategy=strategy
        )
        
        self.sessions[session_id] = session
        
        # 开始同步
        asyncio.create_task(self._execute_sync_session(session))
        
        return session
    
    async def get_sync_status(self, session_id: str) -> Optional[SyncSession]:
        """获取同步状态"""
        return self.sessions.get(session_id)
    
    async def list_sync_sessions(self, device_id: Optional[str] = None) -> List[SyncSession]:
        """列出同步会话"""
        if device_id:
            return [
                session for session in self.sessions.values()
                if session.source_device_id == device_id or session.target_device_id == device_id
            ]
        
        return list(self.sessions.values())
    
    async def add_device(self, device: DeviceInfo) -> None:
        """添加设备"""
        self.connected_devices[device.device_id] = device
    
    async def remove_device(self, device_id: str) -> None:
        """移除设备"""
        if device_id in self.connected_devices:
            del self.connected_devices[device_id]
    
    async def _execute_sync_session(self, session: SyncSession) -> None:
        """执行同步会话"""
        session.status = SyncStatus.SYNCING
        
        try:
            # 获取需要同步的记忆
            local_memories = await self._get_memories_for_sync(session.target_device_id)
            session.total_memories = len(local_memories)
            
            # 分批同步
            for i in range(0, len(local_memories), self.batch_size):
                batch = local_memories[i:i + self.batch_size]
                
                # 同步批次
                await self._sync_batch(session, batch)
                
                # 更新进度
                session.synced_memories = min(session.total_memories, i + self.batch_size)
                
                # 短暂休眠，避免过载
                await asyncio.sleep(0.1)
            
            # 标记完成
            session.status = SyncStatus.COMPLETED
            session.end_time = time.time()
            
        except Exception as e:
            session.status = SyncStatus.FAILED
            session.end_time = time.time()
            print(f"同步会话失败: {e}")
    
    async def _sync_batch(self, session: SyncSession, memories: List[MemoryItem]) -> None:
        """同步一批记忆"""
        for memory in memories:
            try:
                # 创建同步操作
                operation = SyncOperation(
                    id=hashlib.md5(f"{memory.id}_{session.id}".encode()).hexdigest()[:8],
                    memory_id=memory.id,
                    device_id=session.target_device_id,
                    direction=SyncDirection.BIDIRECTIONAL
                )
                
                # 执行同步
                success = await self._sync_single_memory(session, memory, operation)
                
                if success:
                    operation.status = SyncStatus.COMPLETED
                    session.synced_memories += 1
                else:
                    operation.status = SyncStatus.FAILED
                    session.failed_memories += 1
                
                # 添加到会话
                session.operations.append(operation)
                
            except Exception as e:
                print(f"同步记忆失败 {memory.id}: {e}")
                session.failed_memories += 1
    
    async def _sync_single_memory(self, session: SyncSession, memory: MemoryItem, operation: SyncOperation) -> bool:
        """同步单个记忆"""
        try:
            # 这里应该调用远程设备的API来获取远程记忆
            # 由于第二阶段只实现本地同步，这里模拟远程记忆
            remote_memory = await self._simulate_remote_memory(memory, session.target_device_id)
            
            if not remote_memory:
                # 远程不存在，直接推送
                return await self._push_memory(memory, session.target_device_id)
            
            # 检测冲突
            if ConflictResolver.detect_conflict(memory, remote_memory):
                session.conflict_memories += 1
                operation.status = SyncStatus.CONFLICT
                
                # 根据策略解决冲突
                resolved_memory = await self._resolve_conflict(
                    memory, remote_memory, session.strategy, session.source_device_id
                )
                
                # 更新本地和远程
                await self.memory_system.save(resolved_memory)
                await self._push_memory(resolved_memory, session.target_device_id)
                
                operation.conflict_resolution = {"strategy": session.strategy.value}
                operation.merged_memory_id = resolved_memory.id
            
            else:
                # 无冲突，更新较新的版本
                if memory.updated_at > remote_memory.updated_at:
                    await self._push_memory(memory, session.target_device_id)
                else:
                    await self.memory_system.save(remote_memory)
            
            return True
        
        except Exception as e:
            operation.error_message = str(e)
            operation.retry_count += 1
            
            if operation.retry_count < self.max_retries:
                # 重试
                await asyncio.sleep(2 ** operation.retry_count)  # 指数退避
                return await self._sync_single_memory(session, memory, operation)
            
            return False
    
    async def _push_memory(self, memory: MemoryItem, target_device_id: str) -> bool:
        """推送记忆到远程设备"""
        # 第二阶段：模拟推送成功
        # 实际实现中应该通过WebSocket或HTTP API发送到远程设备
        print(f"推送记忆 {memory.id} 到设备 {target_device_id}")
        return True
    
    async def _pull_memory(self, memory_id: str, source_device_id: str) -> Optional[MemoryItem]:
        """从远程设备拉取记忆"""
        # 第二阶段：模拟拉取
        # 实际实现中应该通过WebSocket或HTTP API从远程设备获取
        print(f"从设备 {source_device_id} 拉取记忆 {memory_id}")
        
        # 模拟返回一个记忆
        return None
    
    async def _get_memories_for_sync(self, target_device_id: str) -> List[MemoryItem]:
        """获取需要同步的记忆"""
        # 查询所有需要同步到目标设备的记忆
        query = MemoryQuery(
            device_id=self.local_device_id,
            limit=1000
        )
        
        memories = await self.memory_system.query(query)
        
        # 过滤：只同步最近修改过的记忆
        recent_memories = [
            memory for memory in memories
            if memory.updated_at > time.time() - 86400  # 24小时内
        ]
        
        return recent_memories
    
    async def _simulate_remote_memory(self, memory: MemoryItem, device_id: str) -> Optional[MemoryItem]:
        """模拟远程记忆（用于测试）"""
        # 50%的概率返回模拟的远程记忆
        import random
        if random.random() > 0.5:
            return None
        
        # 创建模拟的远程记忆
        remote_memory = MemoryItem(
            id=memory.id,
            type=memory.type,
            content=memory.content.copy(),
            priority=memory.priority,
            metadata=memory.metadata,
            device_id=device_id,
            related_memories=memory.related_memories.copy()
        )
        
        # 稍微修改内容以模拟冲突
        if isinstance(remote_memory.content, dict) and len(remote_memory.content) > 0:
            first_key = list(remote_memory.content.keys())[0]
            if isinstance(remote_memory.content[first_key], str):
                remote_memory.content[first_key] += " (远程修改)"
        
        # 设置不同的更新时间（可能更旧或更新）
        remote_memory.updated_at = memory.updated_at + random.uniform(-3600, 3600)
        
        return remote_memory
    
    async def _resolve_conflict(self, local_memory: MemoryItem, remote_memory: MemoryItem, 
                               strategy: SyncStrategy, source_device_id: str) -> MemoryItem:
        """解决冲突"""
        if strategy == SyncStrategy.LAST_WRITE_WINS:
            return ConflictResolver.resolve_last_write_wins(local_memory, remote_memory)
        
        elif strategy == SyncStrategy.SOURCE_PRIORITY:
            return ConflictResolver.resolve_source_priority(local_memory, remote_memory, source_device_id)
        
        elif strategy == SyncStrategy.AUTO_MERGE:
            return ConflictResolver.auto_merge(local_memory, remote_memory)
        
        else:
            # 默认使用最后写入获胜
            return ConflictResolver.resolve_last_write_wins(local_memory, remote_memory)
    
    async def _periodic_sync(self) -> None:
        """定期同步"""
        while self.is_running:
            try:
                # 等待同步间隔
                await asyncio.sleep(self.sync_interval)
                
                # 与所有连接的设备同步
                for device_id in list(self.connected_devices.keys()):
                    try:
                        await self.sync_with_device(device_id)
                    except Exception as e:
                        print(f"与设备 {device_id} 定期同步失败: {e}")
            
            except asyncio.CancelledError:
                break
            except Exception as e:
                print(f"定期同步任务出错: {e}")
                await asyncio.sleep(60)  # 出错后等待1分钟
    
    async def _wait_for_sessions(self, timeout: float = 30.0) -> None:
        """等待所有同步会话完成"""
        start_time = time.time()
        
        while self.sessions:
            # 检查超时
            if time.time() - start_time > timeout:
                print(f"等待同步会话超时，剩余 {len(self.sessions)} 个会话")
                break
            
            # 检查是否有活跃的会话
            active_sessions = [
                session for session in self.sessions.values()
                if session.status in [SyncStatus.PENDING, SyncStatus.SYNCING]
            ]
            
            if not active_sessions:
                break
            
            # 等待一段时间
            await asyncio.sleep(1)
    
    def get_stats(self) -> Dict[str, Any]:
        """获取同步统计信息"""
        total_sessions = len(self.sessions)
        completed_sessions = len([s for s in self.sessions.values() if s.status == SyncStatus.COMPLETED])
        failed_sessions = len([s for s in self.sessions.values() if s.status == SyncStatus.FAILED])
        
        total_operations = 0
        completed_operations = 0
        failed_operations = 0
        conflict_operations = 0
        
        for session in self.sessions.values():
            total_operations += len(session.operations)
            completed_operations += len([op for op in session.operations if op.status == SyncStatus.COMPLETED])
            failed_operations += len([op for op in session.operations if op.status == SyncStatus.FAILED])
            conflict_operations += len([op for op in session.operations if op.status == SyncStatus.CONFLICT])
        
        return {
            "connected_devices": len(self.connected_devices),
            "total_sessions": total_sessions,
            "completed_sessions": completed_sessions,
            "failed_sessions": failed_sessions,
            "total_operations": total_operations,
            "completed_operations": completed_operations,
            "failed_operations": failed_operations,
            "conflict_operations": conflict_operations
        }

path: call_aitest.py
content: """
调用智能测试1.0工作区执行AI Skills Testing验收PAO系统
"""
import asyncio
import sys
import os
from pathlib import Path

# 添加项目路径（使用相对路径定位）
_project_root = Path(__file__).parent.parent.resolve()
sys.path.insert(0, str(_project_root))

# 获取Skill目录的相对路径
SKILL_DIR = Path(__file__).parent.resolve()

from task_distributor import TaskDistributor
from src.protocols.task_protocol import WorkerInfo

async def main():
    # 创建分发器
    dist = TaskDistributor("ws_main", "PAO系统")
    
    # 注册智能测试1.0工作区（目标工作区）
    dist.register_worker(WorkerInfo(
        ws_id="ws_aitest",
        ws_name="智能测试1.0",
        host="localhost",
        port=8767,
        status="online",
        capabilities=["ai_skills_testing"]
    ))
    
    print("=" * 60)
    print("[CALL] 正在向智能测试1.0发送AI Skills Testing任务...")
    print("   目标工作区: ws_aitest (智能测试 1.0)")
    print("   任务: 验收 PAO 系统")
    print("=" * 60)
    
    # 分发AI Testing任务（使用动态获取的路径）
    result = await dist.dispatch(
        task_action="AI Skills Testing - 验收PAO系统",
        task_params={
            "skill_path": str(SKILL_DIR),
            "skill_name": "pao",
            "test_type": "full"
        },
        workers=["ws_aitest"],
        task_type="ai_test",
        priority=8
    )
    
    print("\n" + "=" * 60)
    print("[RESULT] 任务执行结果")
    print("=" * 60)
    print(f"任务ID: {result.task_id}")
    print(f"任务动作: {result.task_action}")
    print(f"总工作区数: {result.total_workers}")
    print(f"成功: {result.completed_count}")
    print(f"失败: {result.failed_count}")
    
    for r in result.results:
        print(f"\n--- {r.worker_name} ({r.worker_ws}) ---")
        print(f"状态: {r.status}")
        if r.error:
            print(f"错误: {r.error}")
        if r.result:
            print(f"结果: {r.result}")
    
    print("\n[SUMMARY] 聚合结果:")
    print(result.aggregated_result)

if __name__ == "__main__":
    asyncio.run(main())

path: deployment/config_wizard.py
content: """PAO系统配置向导"""
import json
from pathlib import Path


class ConfigWizard:
    def __init__(self):
        self.config_path = Path.home() / ".config" / "pao" / "config.json"
        self.config = {
            "device_name": "PAO-Device",
            "port": 8080,
            "encryption": True,
            "auto_sync": True,
            "sync_interval": 60
        }

    def run(self):
        """运行配置向导"""
        print("⚙️ PAO系统配置向导")
        print("=" * 50)

        # 设备名称
        device_name = input(f"设备名称 [{self.config['device_name']}]: ").strip()
        if device_name:
            self.config["device_name"] = device_name

        # 端口
        port = input(f"通信端口 [{self.config['port']}]: ").strip()
        if port and port.isdigit():
            self.config["port"] = int(port)

        # 加密
        encryption = input("启用加密 (y/n) [y]: ").strip().lower()
        self.config["encryption"] = encryption != "n"

        # 自动同步
        auto_sync = input("启用自动同步 (y/n) [y]: ").strip().lower()
        self.config["auto_sync"] = auto_sync != "n"

        # 保存配置
        self._save_config()
        print("✅ 配置已保存!")

    def _save_config(self):
        """保存配置"""
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
        with open(self.config_path, "w", encoding="utf-8") as f:
            json.dump(self.config, f, indent=2)


if __name__ == "__main__":
    wizard = ConfigWizard()
    wizard.run()


path: deployment/installer.py
content: """PAO系统安装程序"""
import os
import sys
import shutil
import subprocess
from pathlib import Path


class Installer:
    def __init__(self):
        self.install_dir = Path.home() / ".pao"
        self.config_dir = Path.home() / ".config" / "pao"

    def install(self):
        print("🚀 PAO系统安装程序")
        print("=" * 50)

        # 检查Python版本
        if sys.version_info < (3, 9):
            print("❌ 需要 Python 3.9 或更高版本")
            return False

        print(f"📦 安装目录: {self.install_dir}")
        print(f"⚙️ 配置目录: {self.config_dir}")

        # 创建目录
        self._create_directories()

        # 安装依赖
        self._install_dependencies()

        # 复制配置文件
        self._copy_config()

        print()
        print("✅ 安装完成!")
        print(f"运行 'python -m pao' 启动系统")
        return True

    def _create_directories(self):
        """创建必要的目录"""
        print("📁 创建目录结构...")
        self.install_dir.mkdir(parents=True, exist_ok=True)
        self.config_dir.mkdir(parents=True, exist_ok=True)

    def _install_dependencies(self):
        """安装依赖"""
        print("📚 安装依赖...")
        try:
            subprocess.run(
                [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
                check=True
            )
        except subprocess.CalledProcessError:
            print("⚠️ 依赖安装失败，请手动运行: pip install -r requirements.txt")

    def _copy_config(self):
        """复制配置文件"""
        print("📋 复制配置文件...")
        config_template = self.install_dir / "config_template.json"
        if not config_template.exists():
            config_template.write_text('{"device_name": "PAO-Device", "port": 8080}')

    def uninstall(self):
        """卸载系统"""
        print("🗑️ 正在卸载PAO系统...")
        shutil.rmtree(self.install_dir, ignore_errors=True)
        shutil.rmtree(self.config_dir, ignore_errors=True)
        print("✅ 卸载完成")


if __name__ == "__main__":
    installer = Installer()
    if "--uninstall" in sys.argv:
        installer.uninstall()
    else:
        installer.install()


path: deployment/updater.py
content: """PAO系统更新程序"""
import os
import sys
import subprocess
from pathlib import Path


class Updater:
    def __init__(self):
        self.current_version = "1.0.0"

    def check_update(self):
        """检查更新"""
        print("🔍 检查更新...")
        # 模拟检查更新
        latest_version = self.current_version
        if latest_version > self.current_version:
            print(f"📢 发现新版本: {latest_version}")
            return True
        else:
            print("✅ 当前已是最新版本")
            return False

    def update(self):
        """执行更新"""
        if not self.check_update():
            return

        print("📥 下载更新...")
        # 模拟更新过程
        print("✅ 更新完成!")
        print(f"版本: {self.current_version}")


if __name__ == "__main__":
    updater = Updater()
    updater.update()


path: docs/api_reference.md
content: # PAO API 参考文档

## 核心模块

### DeviceDiscovery

设备发现模块，负责局域网内设备自动发现。

```python
from device_discovery import DeviceDiscovery

device = DeviceDiscovery(name="MyDevice")
await device.start()
devices = device.get_discovered_devices()
await device.stop()
```

### Communication

通信模块，负责设备间消息传递。

```python
from communication import Communication

comm = Communication(device)
await comm.connect_to(peer_id)
await comm.send_message(peer_id, {"type": "test"})
await comm.disconnect()
```

### SkillManager

技能管理器，负责AI技能的搜索、加载和评分。

```python
from skill_manager import SkillManager

manager = SkillManager()
await manager.initialize()
skills = await manager.search_skills("python")
await manager.apply_skill("skill_name", params, score, feedback)
stats = await manager.get_skill_stats("skill_name")
```

### ContextAwareness

情境感知模块，负责收集和识别使用情境。

```python
from context_awareness import ContextAwareness

awareness = ContextAwareness()
await awareness.register_default_scenes()
contexts = await awareness.collect_all()
scene = await awareness.recognize_scene()
```

### LearningLoop

学习循环模块，负责处理用户反馈并持续改进。

```python
from learning_loop import LearningLoop, FeedbackType

loop = LearningLoop()
await loop.start()
await loop.submit_feedback(FeedbackType.EXPLICIT, "skill", params, score, feedback)
status = await loop.get_learning_status()
await loop.stop()
```

### SyncManager

同步管理器，负责多设备间数据同步。

```python
from sync import SyncManager

sync = SyncManager(device)
await sync.connect_peer(peer_id)
await sync.sync_data(data)
status = sync.get_sync_status()
await sync.disconnect()
```

## 配置

配置文件位于: `~/.config/pao/config.json`

```json
{
    "device_name": "PAO-Device",
    "port": 8080,
    "encryption": true,
    "auto_sync": true,
    "sync_interval": 60
}
```


path: docs/user_guide.md
content: # PAO 用户指南

## 简介

PAO (Personal AI Operating System) 是一个跨设备、去中心化的AI协同系统。

## 快速开始

### 安装

```bash
cd pao-system
python deployment/installer.py
```

### 配置

```bash
python deployment/config_wizard.py
```

### 运行

```bash
python -m pao
```

## 核心功能

### 设备发现
PAO会自动发现局域网内的其他PAO设备。

### 技能管理
系统会自动管理和进化AI技能。

### 情境感知
根据使用场景自动调整行为。

### 数据同步
多设备间自动同步数据和状态。

## 故障排除

### 设备无法发现
- 检查网络连接
- 确认防火墙设置
- 重启PAO服务

### 同步失败
- 检查网络延迟
- 确认端口未被占用
- 查看日志获取详细信息


path: examples/discovery_demo.py
content: #!/usr/bin/env python3
"""
设备发现演示
演示 PAO 系统的设备发现功能
"""

import asyncio
import sys
import os

# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from src.core.device import DeviceRegistry, DeviceInfo, DeviceType
from src.core.discovery import DeviceDiscoveryService
from src.core.config import PAOConfig, get_config_manager


async def simple_discovery_demo():
    """简单设备发现演示"""
    print("=== PAO 设备发现演示 ===")
    print()
    
    # 创建配置
    config = PAOConfig(device_name="Demo-Device")
    
    # 创建设备注册表
    device_registry = DeviceRegistry()
    
    # 定义回调函数
    def on_device_discovered(device: DeviceInfo):
        print(f"🎯 发现新设备: {device.name}")
        print(f"   类型: {device.device_type.value}")
        print(f"   地址: {device.ip_address}:{device.port}")
        print(f"   能力: {[cap.value for cap in device.capabilities]}")
        print()
    
    def on_device_lost(device_id: str):
        print(f"⚠️  设备丢失: {device_id}")
        print()
    
    # 创建设备发现服务
    discovery_service = DeviceDiscoveryService(
        config=config,
        device_registry=device_registry,
        on_device_discovered=on_device_discovered,
        on_device_lost=on_device_lost
    )
    
    print(f"本地设备: {discovery_service.local_device.name}")
    print(f"IP地址: {discovery_service.local_device.ip_address}")
    print(f"设备ID: {discovery_service.local_device.device_id}")
    print()
    
    # 启动发现服务
    print("启动设备发现服务...")
    await discovery_service.start()
    
    try:
        # 等待一段时间发现设备
        print("等待发现设备 (10秒)...")
        await asyncio.sleep(10)
        
        # 显示发现的设备
        devices = device_registry.list_devices()
        print(f"\n📊 总共发现 {len(devices)} 个设备:")
        
        for i, device in enumerate(devices, 1):
            print(f"{i}. {device.name} ({device.device_type.value})")
            print(f"   ID: {device.device_id}")
            print(f"   主机名: {device.hostname}")
            print(f"   IP: {device.ip_address}:{device.port}")
            print(f"   能力: {[cap.value for cap in device.capabilities]}")
            print(f"   首次发现: {device.first_seen.strftime('%Y-%m-%d %H:%M:%S')}")
            print()
        
        # 保持运行，等待更多设备
        print("持续监听网络设备...")
        print("按 Ctrl+C 停止")
        
        while True:
            # 显示在线设备数量
            online_devices = device_registry.list_online_devices()
            print(f"\r在线设备: {len(online_devices)}", end="", flush=True)
            await asyncio.sleep(1)
            
    except KeyboardInterrupt:
        print("\n\n接收到停止信号")
    finally:
        # 停止发现服务
        print("停止设备发现服务...")
        await discovery_service.stop()
        print("演示结束")


async def advanced_discovery_demo():
    """高级设备发现演示"""
    print("=== PAO 高级设备发现演示 ===")
    print()
    
    # 使用配置管理器
    config_manager = get_config_manager()
    config = config_manager.config
    
    # 更新配置
    config.device_name = "Advanced-Demo"
    config.network.discovery_port = 8888
    config_manager.save_config(config)
    
    # 创建设备注册表
    device_registry = DeviceRegistry()
    
    # 手动添加一些模拟设备
    mock_device1 = DeviceInfo(
        name="Mock-Laptop",
        device_type=DeviceType.LAPTOP,
        ip_address="192.168.1.100",
        port=8765,
        capabilities=["compute", "storage", "display", "input"]
    )
    
    mock_device2 = DeviceInfo(
        name="Mock-Phone",
        device_type=DeviceType.PHONE,
        ip_address="192.168.1.101",
        port=8765,
        capabilities=["compute", "storage", "sensors"]
    )
    
    device_registry.register_device(mock_device1)
    device_registry.register_device(mock_device2)
    
    print("已注册模拟设备:")
    for device in device_registry.list_devices():
        print(f"  - {device.name}: {device.device_type.value}")
    print()
    
    # 创建设备发现服务
    discovery_service = DeviceDiscoveryService(
        config=config,
        device_registry=device_registry
    )
    
    # 启动服务
    print("启动高级发现服务...")
    await discovery_service.start()
    
    try:
        # 模拟网络发现
        print("模拟网络发现过程...")
        
        for i in range(5):
            print(f"扫描周期 {i+1}/5...")
            
            # 模拟发现新设备
            if i == 2:
                new_device = DeviceInfo(
                    name=f"New-Device-{i}",
                    device_type=DeviceType.IOT,
                    ip_address=f"192.168.1.{150 + i}",
                    port=8765
                )
                device_registry.register_device(new_device)
                print(f"  发现新设备: {new_device.name}")
            
            # 显示设备统计
            online_count = len(device_registry.list_online_devices())
            total_count = len(device_registry.list_devices())
            print(f"  在线: {online_count}/{total_count} 设备")
            
            await asyncio.sleep(2)
        
        # 测试设备查找功能
        print("\n测试设备查找功能:")
        
        # 查找有计算能力的设备
        compute_devices = device_registry.find_device_by_capability("compute")
        print(f"计算设备: {len(compute_devices)} 个")
        
        # 查找有显示能力的设备
        display_devices = device_registry.find_device_by_capability("display")
        print(f"显示设备: {len(display_devices)} 个")
        
        # 清理离线设备
        print("\n清理离线设备...")
        device_registry.cleanup_offline_devices(timeout_seconds=1)
        
        final_count = len(device_registry.list_devices())
        print(f"清理后剩余设备: {final_count} 个")
        
    except KeyboardInterrupt:
        print("\n演示中断")
    finally:
        await discovery_service.stop()
        print("高级演示结束")


async def network_scan_demo():
    """网络扫描演示"""
    print("=== PAO 网络扫描演示 ===")
    print()
    
    from src.core.discovery import discovery_service
    
    config = PAOConfig(device_name="Scanner-Device")
    device_registry = DeviceRegistry()
    
    print("开始网络扫描...")
    
    # 使用上下文管理器
    async with discovery_service(config, device_registry) as service:
        print(f"扫描设备: {service.local_device.name}")
        print(f"扫描端口: {service.discovery_config.port}")
        print()
        
        # 执行主动扫描
        print("执行主动扫描 (5秒)...")
        devices = await service.discover_devices(timeout=5)
        
        if devices:
            print(f"\n扫描结果 ({len(devices)} 个设备):")
            for device in devices:
                status = "🟢 在线" if device.last_seen.timestamp() > 0 else "⚪ 离线"
                print(f"{status} {device.name} - {device.ip_address}")
        else:
            print("\n未发现任何设备")
        
        print("\n扫描完成")


def main():
    """主函数"""
    import argparse
    
    parser = argparse.ArgumentParser(description="PAO 设备发现演示")
    parser.add_argument("--demo", choices=["simple", "advanced", "scan", "all"],
                       default="simple", help="选择演示类型")
    
    args = parser.parse_args()
    
    try:
        if args.demo == "simple":
            asyncio.run(simple_discovery_demo())
        elif args.demo == "advanced":
            asyncio.run(advanced_discovery_demo())
        elif args.demo == "scan":
            asyncio.run(network_scan_demo())
        elif args.demo == "all":
            print("运行所有演示...")
            asyncio.run(simple_discovery_demo())
            print("\n" + "="*50 + "\n")
            asyncio.run(advanced_discovery_demo())
            print("\n" + "="*50 + "\n")
            asyncio.run(network_scan_demo())
    except KeyboardInterrupt:
        print("\n演示被用户中断")
    except Exception as e:
        print(f"演示出错: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    main()

path: examples/memory_sync_demo.py
content: """
记忆同步演示
展示 PAO 系统的记忆同步功能
"""

import asyncio
import sys
import os
from pathlib import Path

# 添加项目根目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from src.core.memory import MemorySystem, MemoryItem, MemoryType, MemoryPriority, MemoryQuery
from src.core.sync import SyncEngine, SyncStrategy
from src.core.device import DeviceInfo, DeviceType, DeviceCapability


async def create_sample_memories(memory_system: MemorySystem, device_id: str) -> None:
    """创建示例记忆"""
    
    # 对话记忆
    conversation_memory = MemoryItem(
        type=MemoryType.CONVERSATION,
        content={
            "user": "你好，今天天气怎么样？",
            "assistant": "今天天气晴朗，温度适宜。",
            "context": "天气查询对话"
        },
        priority=MemoryPriority.MEDIUM,
        device_id=device_id
    )
    conversation_memory.metadata.tags = ["天气", "对话", "日常"]
    conversation_memory.metadata.source = "user:alice"
    
    await memory_system.save(conversation_memory)
    print(f"创建对话记忆: {conversation_memory.id}")
    
    # 知识记忆
    knowledge_memory = MemoryItem(
        type=MemoryType.KNOWLEDGE,
        content={
            "topic": "Python 异步编程",
            "content": "asyncio 是 Python 的异步编程库，使用 async/await 语法。",
            "source": "官方文档"
        },
        priority=MemoryPriority.HIGH,
        device_id=device_id
    )
    knowledge_memory.metadata.tags = ["Python", "编程", "异步"]
    knowledge_memory.metadata.source = "system:learning"
    
    await memory_system.save(knowledge_memory)
    print(f"创建知识记忆: {knowledge_memory.id}")
    
    # 任务记忆
    task_memory = MemoryItem(
        type=MemoryType.TASK,
        content={
            "title": "完成 PAO 系统第二阶段",
            "description": "实现本地记忆系统和基本同步功能",
            "status": "in_progress",
            "deadline": "2026-04-16"
        },
        priority=MemoryPriority.CRITICAL,
        device_id=device_id
    )
    task_memory.metadata.tags = ["任务", "PAO", "开发"]
    task_memory.metadata.source = "user:developer"
    
    await memory_system.save(task_memory)
    print(f"创建任务记忆: {task_memory.id}")
    
    print(f"总共创建了 3 个示例记忆")


async def demonstrate_memory_operations(memory_system: MemorySystem) -> None:
    """演示记忆操作"""
    
    print("\n=== 记忆操作演示 ===")
    
    # 查询所有记忆
    all_memories = await memory_system.query(MemoryQuery(limit=10))
    print(f"总记忆数量: {len(all_memories)}")
    
    # 按类型查询
    conversation_query = MemoryQuery(
        type=MemoryType.CONVERSATION,
        limit=5
    )
    conversations = await memory_system.query(conversation_query)
    print(f"对话记忆数量: {len(conversations)}")
    
    # 按标签查询
    tag_query = MemoryQuery(
        tags=["Python"],
        limit=5
    )
    python_memories = await memory_system.query(tag_query)
    print(f"Python相关记忆数量: {len(python_memories)}")
    
    # 获取记忆详情
    if all_memories:
        first_memory = all_memories[0]
        print(f"\n第一个记忆详情:")
        print(f"  ID: {first_memory.id}")
        print(f"  类型: {first_memory.type.value}")
        print(f"  内容: {first_memory.content}")
        print(f"  优先级: {first_memory.priority.value}")
        print(f"  标签: {first_memory.metadata.tags}")
        print(f"  创建时间: {first_memory.created_at}")
        print(f"  设备ID: {first_memory.device_id}")
    
    # 记忆统计
    stats = memory_system.get_stats()
    print(f"\n记忆统计:")
    print(f"  总记忆数: {stats['total_memories']}")
    print(f"  按类型分布: {stats['by_type']}")
    if stats.get('by_device'):
        print(f"  按设备分布: {stats['by_device']}")


async def demonstrate_sync_operations(memory_system: MemorySystem, local_device_id: str) -> None:
    """演示同步操作"""
    
    print("\n=== 同步操作演示 ===")
    
    # 创建同步引擎
    sync_engine = SyncEngine(memory_system, local_device_id)
    
    # 模拟远程设备
    remote_device = DeviceInfo(
        device_id="remote-pc-001",
        name="远程办公电脑",
        device_type=DeviceType.DESKTOP,
        ip_address="192.168.1.100",
        port=8765,
        capabilities=[DeviceCapability.MEMORY_SYNC, DeviceCapability.FILE_SHARING]
    )
    
    # 添加设备
    await sync_engine.add_device(remote_device)
    print(f"添加设备: {remote_device.name} ({remote_device.device_id})")
    
    # 启动同步引擎
    await sync_engine.start()
    print("同步引擎已启动")
    
    # 执行同步
    print(f"开始与设备 {remote_device.device_id} 同步...")
    session = await sync_engine.sync_with_device(
        remote_device.device_id,
        strategy=SyncStrategy.LAST_WRITE_WINS
    )
    
    print(f"同步会话已创建: {session.id}")
    print(f"同步策略: {session.strategy.value}")
    
    # 等待同步完成
    print("等待同步完成...")
    for i in range(10):  # 最多等待10秒
        await asyncio.sleep(1)
        
        updated_session = await sync_engine.get_sync_status(session.id)
        if updated_session and updated_session.status.value in ["completed", "failed"]:
            print(f"\n同步完成!")
            print(f"  状态: {updated_session.status.value}")
            print(f"  总记忆数: {updated_session.total_memories}")
            print(f"  已同步: {updated_session.synced_memories}")
            print(f"  失败数: {updated_session.failed_memories}")
            print(f"  冲突数: {updated_session.conflict_memories}")
            print(f"  耗时: {updated_session.end_time - updated_session.start_time:.2f}秒")
            break
        else:
            print(f".", end="", flush=True)
    
    # 显示同步统计
    sync_stats = sync_engine.get_stats()
    print(f"\n同步引擎统计:")
    print(f"  已连接设备: {sync_stats['connected_devices']}")
    print(f"  总同步会话: {sync_stats['total_sessions']}")
    print(f"  成功会话: {sync_stats['completed_sessions']}")
    print(f"  失败会话: {sync_stats['failed_sessions']}")
    print(f"  总操作数: {sync_stats['total_operations']}")
    print(f"  成功操作: {sync_stats['completed_operations']}")
    print(f"  失败操作: {sync_stats['failed_operations']}")
    print(f"  冲突操作: {sync_stats['conflict_operations']}")
    
    # 停止同步引擎
    await sync_engine.stop()
    print("同步引擎已停止")


async def demonstrate_conflict_resolution() -> None:
    """演示冲突解决"""
    
    print("\n=== 冲突解决演示 ===")
    
    from src.core.sync import ConflictResolver
    
    # 创建两个有冲突的记忆（模拟本地和远程）
    local_memory = MemoryItem(
        type=MemoryType.CONVERSATION,
        content={
            "user": "今天吃什么？",
            "assistant": "建议吃中餐，比如宫保鸡丁。",
            "context": "午餐建议"
        },
        priority=MemoryPriority.MEDIUM,
        device_id="local-pc-001"
    )
    local_memory.updated_at = time.time() - 3600  # 1小时前
    
    remote_memory = MemoryItem(
        type=MemoryType.CONVERSATION,
        content={
            "user": "今天吃什么？",
            "assistant": "建议吃西餐，比如意大利面。",  # 冲突内容
            "context": "午餐建议"
        },
        priority=MemoryPriority.HIGH,  # 冲突优先级
        device_id="remote-pc-001"
    )
    remote_memory.updated_at = time.time() - 1800  # 30分钟前（更新）
    
    print("冲突检测:")
    print(f"  本地记忆更新时间: {local_memory.updated_at}")
    print(f"  远程记忆更新时间: {remote_memory.updated_at}")
    print(f"  本地记忆优先级: {local_memory.priority.value}")
    print(f"  远程记忆优先级: {remote_memory.priority.value}")
    print(f"  本地记忆内容: {local_memory.content['assistant']}")
    print(f"  远程记忆内容: {remote_memory.content['assistant']}")
    
    # 检测冲突
    has_conflict = ConflictResolver.detect_conflict(local_memory, remote_memory)
    print(f"\n是否存在冲突: {has_conflict}")
    
    if has_conflict:
        print("\n不同策略的解决结果:")
        
        # 最后写入获胜
        last_write_wins = ConflictResolver.resolve_last_write_wins(local_memory, remote_memory)
        print(f"  最后写入获胜: {last_write_wins.content['assistant']} (设备: {last_write_wins.device_id})")
        
        # 源设备优先（假设源设备是本地）
        source_priority = ConflictResolver.resolve_source_priority(local_memory, remote_memory, "local-pc-001")
        print(f"  源设备优先: {source_priority.content['assistant']} (设备: {source_priority.device_id})")
        
        # 自动合并
        auto_merged = ConflictResolver.auto_merge(local_memory, remote_memory)
        print(f"  自动合并: {auto_merged.content['assistant']}")
        print(f"  合并后优先级: {auto_merged.priority.value}")
        print(f"  合并后标签数: {len(auto_merged.metadata.tags)}")


async def main() -> None:
    """主演示函数"""
    
    print("=== PAO 系统记忆同步演示 ===")
    print("第二阶段：本地记忆系统与基本同步")
    print()
    
    # 创建记忆系统
    memory_system = MemorySystem()
    await memory_system.load()
    
    # 本地设备ID
    local_device_id = "demo-pc-001"
    
    # 1. 创建示例记忆
    print("1. 创建示例记忆...")
    await create_sample_memories(memory_system, local_device_id)
    
    # 2. 演示记忆操作
    await demonstrate_memory_operations(memory_system)
    
    # 3. 演示同步操作
    await demonstrate_sync_operations(memory_system, local_device_id)
    
    # 4. 演示冲突解决
    await demonstrate_conflict_resolution()
    
    # 5. 清理演示数据（可选）
    print("\n5. 清理演示数据...")
    # 在实际使用中，这里可以删除演示创建的记忆
    
    print("\n=== 演示完成 ===")
    print("PAO 系统记忆同步功能演示完毕。")
    print("第二阶段核心功能已实现：")
    print("  ✓ 本地记忆存储和管理")
    print("  ✓ 记忆查询和检索")
    print("  ✓ 跨设备同步引擎")
    print("  ✓ 冲突检测和解决")
    print("  ✓ 多种同步策略")


if __name__ == "__main__":
    import time
    
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n演示被用户中断")
    except Exception as e:
        print(f"演示出错: {e}")
        import traceback
        traceback.print_exc()

path: htmlcov/coverage_html.js
content: // Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt

// Coverage.py HTML report browser code.
/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
/*global coverage: true, document, window, $ */

coverage = {};

// General helpers
function debounce(callback, wait) {
    let timeoutId = null;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            callback.apply(this, args);
        }, wait);
    };
};

function checkVisible(element) {
    const rect = element.getBoundingClientRect();
    const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight);
    const viewTop = 30;
    return !(rect.bottom < viewTop || rect.top >= viewBottom);
}

function on_click(sel, fn) {
    const elt = document.querySelector(sel);
    if (elt) {
        elt.addEventListener("click", fn);
    }
}

// Helpers for table sorting
function getCellValue(row, column = 0) {
    const cell = row.cells[column]  // nosemgrep: eslint.detect-object-injection
    if (cell.childElementCount == 1) {
        const child = cell.firstElementChild
        if (child instanceof HTMLTimeElement && child.dateTime) {
            return child.dateTime
        } else if (child instanceof HTMLDataElement && child.value) {
            return child.value
        }
    }
    return cell.innerText || cell.textContent;
}

function rowComparator(rowA, rowB, column = 0) {
    let valueA = getCellValue(rowA, column);
    let valueB = getCellValue(rowB, column);
    if (!isNaN(valueA) && !isNaN(valueB)) {
        return valueA - valueB
    }
    return valueA.localeCompare(valueB, undefined, {numeric: true});
}

function sortColumn(th) {
    // Get the current sorting direction of the selected header,
    // clear state on other headers and then set the new sorting direction
    const currentSortOrder = th.getAttribute("aria-sort");
    [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none"));
    if (currentSortOrder === "none") {
        th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending");
    } else {
        th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending");
    }

    const column = [...th.parentElement.cells].indexOf(th)

    // Sort all rows and afterwards append them in order to move them in the DOM
    Array.from(th.closest("table").querySelectorAll("tbody tr"))
        .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.getAttribute("aria-sort") === "ascending" ? 1 : -1))
        .forEach(tr => tr.parentElement.appendChild(tr) );
}

// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
coverage.assign_shortkeys = function () {
    document.querySelectorAll("[data-shortcut]").forEach(element => {
        document.addEventListener("keypress", event => {
            if (event.target.tagName.toLowerCase() === "input") {
                return; // ignore keypress from search filter
            }
            if (event.key === element.dataset.shortcut) {
                element.click();
            }
        });
    });
};

// Create the events for the filter box.
coverage.wire_up_filter = function () {
    // Cache elements.
    const table = document.querySelector("table.index");
    const table_body_rows = table.querySelectorAll("tbody tr");
    const no_rows = document.getElementById("no_rows");

    // Observe filter keyevents.
    document.getElementById("filter").addEventListener("input", debounce(event => {
        // Keep running total of each metric, first index contains number of shown rows
        const totals = new Array(table.rows[0].cells.length).fill(0);
        // Accumulate the percentage as fraction
        totals[totals.length - 1] = { "numer": 0, "denom": 0 };  // nosemgrep: eslint.detect-object-injection

        // Hide / show elements.
        table_body_rows.forEach(row => {
            if (!row.cells[0].textContent.includes(event.target.value)) {
                // hide
                row.classList.add("hidden");
                return;
            }

            // show
            row.classList.remove("hidden");
            totals[0]++;

            for (let column = 1; column < totals.length; column++) {
                // Accumulate dynamic totals
                cell = row.cells[column]  // nosemgrep: eslint.detect-object-injection
                if (column === totals.length - 1) {
                    // Last column contains percentage
                    const [numer, denom] = cell.dataset.ratio.split(" ");
                    totals[column]["numer"] += parseInt(numer, 10);  // nosemgrep: eslint.detect-object-injection
                    totals[column]["denom"] += parseInt(denom, 10);  // nosemgrep: eslint.detect-object-injection
                } else {
                    totals[column] += parseInt(cell.textContent, 10);  // nosemgrep: eslint.detect-object-injection
                }
            }
        });

        // Show placeholder if no rows will be displayed.
        if (!totals[0]) {
            // Show placeholder, hide table.
            no_rows.style.display = "block";
            table.style.display = "none";
            return;
        }

        // Hide placeholder, show table.
        no_rows.style.display = null;
        table.style.display = null;

        const footer = table.tFoot.rows[0];
        // Calculate new dynamic sum values based on visible rows.
        for (let column = 1; column < totals.length; column++) {
            // Get footer cell element.
            const cell = footer.cells[column];  // nosemgrep: eslint.detect-object-injection

            // Set value into dynamic footer cell element.
            if (column === totals.length - 1) {
                // Percentage column uses the numerator and denominator,
                // and adapts to the number of decimal places.
                const match = /\.([0-9]+)/.exec(cell.textContent);
                const places = match ? match[1].length : 0;
                const { numer, denom } = totals[column];  // nosemgrep: eslint.detect-object-injection
                cell.dataset.ratio = `${numer} ${denom}`;
                // Check denom to prevent NaN if filtered files contain no statements
                cell.textContent = denom
                    ? `${(numer * 100 / denom).toFixed(places)}%`
                    : `${(100).toFixed(places)}%`;
            } else {
                cell.textContent = totals[column];  // nosemgrep: eslint.detect-object-injection
            }
        }
    }));

    // Trigger change event on setup, to force filter on page refresh
    // (filter value may still be present).
    document.getElementById("filter").dispatchEvent(new Event("input"));
};

coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2";

// Loaded on index.html
coverage.index_ready = function () {
    coverage.assign_shortkeys();
    coverage.wire_up_filter();
    document.querySelectorAll("[data-sortable] th[aria-sort]").forEach(
        th => th.addEventListener("click", e => sortColumn(e.target))
    );

    // Look for a localStorage item containing previous sort settings:
    const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE);

    if (stored_list) {
        const {column, direction} = JSON.parse(stored_list);
        const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column];  // nosemgrep: eslint.detect-object-injection
        th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending");
        th.click()
    }

    // Watch for page unload events so we can save the final sort settings:
    window.addEventListener("unload", function () {
        const th = document.querySelector('[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]');
        if (!th) {
            return;
        }
        localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({
            column: [...th.parentElement.cells].indexOf(th),
            direction: th.getAttribute("aria-sort"),
        }));
    });

    on_click(".button_prev_file", coverage.to_prev_file);
    on_click(".button_next_file", coverage.to_next_file);

    on_click(".button_show_hide_help", coverage.show_hide_help);
};

// -- pyfile stuff --

coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";

coverage.pyfile_ready = function () {
    // If we're directed to a particular line number, highlight the line.
    var frag = location.hash;
    if (frag.length > 2 && frag[1] === "t") {
        document.querySelector(frag).closest(".n").classList.add("highlight");
        coverage.set_sel(parseInt(frag.substr(2), 10));
    } else {
        coverage.set_sel(0);
    }

    on_click(".button_toggle_run", coverage.toggle_lines);
    on_click(".button_toggle_mis", coverage.toggle_lines);
    on_click(".button_toggle_exc", coverage.toggle_lines);
    on_click(".button_toggle_par", coverage.toggle_lines);

    on_click(".button_next_chunk", coverage.to_next_chunk_nicely);
    on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely);
    on_click(".button_top_of_page", coverage.to_top);
    on_click(".button_first_chunk", coverage.to_first_chunk);

    on_click(".button_prev_file", coverage.to_prev_file);
    on_click(".button_next_file", coverage.to_next_file);
    on_click(".button_to_index", coverage.to_index);

    on_click(".button_show_hide_help", coverage.show_hide_help);

    coverage.filters = undefined;
    try {
        coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
    } catch(err) {}

    if (coverage.filters) {
        coverage.filters = JSON.parse(coverage.filters);
    }
    else {
        coverage.filters = {run: false, exc: true, mis: true, par: true};
    }

    for (cls in coverage.filters) {
        coverage.set_line_visibilty(cls, coverage.filters[cls]);  // nosemgrep: eslint.detect-object-injection
    }

    coverage.assign_shortkeys();
    coverage.init_scroll_markers();
    coverage.wire_up_sticky_header();

    document.querySelectorAll("[id^=ctxs]").forEach(
        cbox => cbox.addEventListener("click", coverage.expand_contexts)
    );

    // Rebuild scroll markers when the window height changes.
    window.addEventListener("resize", coverage.build_scroll_markers);
};

coverage.toggle_lines = function (event) {
    const btn = event.target.closest("button");
    const category = btn.value
    const show = !btn.classList.contains("show_" + category);
    coverage.set_line_visibilty(category, show);
    coverage.build_scroll_markers();
    coverage.filters[category] = show;
    try {
        localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
    } catch(err) {}
};

coverage.set_line_visibilty = function (category, should_show) {
    const cls = "show_" + category;
    const btn = document.querySelector(".button_toggle_" + category);
    if (btn) {
        if (should_show) {
            document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls));
            btn.classList.add(cls);
        }
        else {
            document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls));
            btn.classList.remove(cls);
        }
    }
};

// Return the nth line div.
coverage.line_elt = function (n) {
    return document.getElementById("t" + n)?.closest("p");
};

// Set the selection.  b and e are line numbers.
coverage.set_sel = function (b, e) {
    // The first line selected.
    coverage.sel_begin = b;
    // The next line not selected.
    coverage.sel_end = (e === undefined) ? b+1 : e;
};

coverage.to_top = function () {
    coverage.set_sel(0, 1);
    coverage.scroll_window(0);
};

coverage.to_first_chunk = function () {
    coverage.set_sel(0, 1);
    coverage.to_next_chunk();
};

coverage.to_prev_file = function () {
    window.location = document.getElementById("prevFileLink").href;
}

coverage.to_next_file = function () {
    window.location = document.getElementById("nextFileLink").href;
}

coverage.to_index = function () {
    location.href = document.getElementById("indexLink").href;
}

coverage.show_hide_help = function () {
    const helpCheck = document.getElementById("help_panel_state")
    helpCheck.checked = !helpCheck.checked;
}

// Return a string indicating what kind of chunk this line belongs to,
// or null if not a chunk.
coverage.chunk_indicator = function (line_elt) {
    const classes = line_elt?.className;
    if (!classes) {
        return null;
    }
    const match = classes.match(/\bshow_\w+\b/);
    if (!match) {
        return null;
    }
    return match[0];
};

coverage.to_next_chunk = function () {
    const c = coverage;

    // Find the start of the next colored chunk.
    var probe = c.sel_end;
    var chunk_indicator, probe_line;
    while (true) {
        probe_line = c.line_elt(probe);
        if (!probe_line) {
            return;
        }
        chunk_indicator = c.chunk_indicator(probe_line);
        if (chunk_indicator) {
            break;
        }
        probe++;
    }

    // There's a next chunk, `probe` points to it.
    var begin = probe;

    // Find the end of this chunk.
    var next_indicator = chunk_indicator;
    while (next_indicator === chunk_indicator) {
        probe++;
        probe_line = c.line_elt(probe);
        next_indicator = c.chunk_indicator(probe_line);
    }
    c.set_sel(begin, probe);
    c.show_selection();
};

coverage.to_prev_chunk = function () {
    const c = coverage;

    // Find the end of the prev colored chunk.
    var probe = c.sel_begin-1;
    var probe_line = c.line_elt(probe);
    if (!probe_line) {
        return;
    }
    var chunk_indicator = c.chunk_indicator(probe_line);
    while (probe > 1 && !chunk_indicator) {
        probe--;
        probe_line = c.line_elt(probe);
        if (!probe_line) {
            return;
        }
        chunk_indicator = c.chunk_indicator(probe_line);
    }

    // There's a prev chunk, `probe` points to its last line.
    var end = probe+1;

    // Find the beginning of this chunk.
    var prev_indicator = chunk_indicator;
    while (prev_indicator === chunk_indicator) {
        probe--;
        if (probe <= 0) {
            return;
        }
        probe_line = c.line_elt(probe);
        prev_indicator = c.chunk_indicator(probe_line);
    }
    c.set_sel(probe+1, end);
    c.show_selection();
};

// Returns 0, 1, or 2: how many of the two ends of the selection are on
// the screen right now?
coverage.selection_ends_on_screen = function () {
    if (coverage.sel_begin === 0) {
        return 0;
    }

    const begin = coverage.line_elt(coverage.sel_begin);
    const end = coverage.line_elt(coverage.sel_end-1);

    return (
        (checkVisible(begin) ? 1 : 0)
        + (checkVisible(end) ? 1 : 0)
    );
};

coverage.to_next_chunk_nicely = function () {
    if (coverage.selection_ends_on_screen() === 0) {
        // The selection is entirely off the screen:
        // Set the top line on the screen as selection.

        // This will select the top-left of the viewport
        // As this is most likely the span with the line number we take the parent
        const line = document.elementFromPoint(0, 0).parentElement;
        if (line.parentElement !== document.getElementById("source")) {
            // The element is not a source line but the header or similar
            coverage.select_line_or_chunk(1);
        } else {
            // We extract the line number from the id
            coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
        }
    }
    coverage.to_next_chunk();
};

coverage.to_prev_chunk_nicely = function () {
    if (coverage.selection_ends_on_screen() === 0) {
        // The selection is entirely off the screen:
        // Set the lowest line on the screen as selection.

        // This will select the bottom-left of the viewport
        // As this is most likely the span with the line number we take the parent
        const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement;
        if (line.parentElement !== document.getElementById("source")) {
            // The element is not a source line but the header or similar
            coverage.select_line_or_chunk(coverage.lines_len);
        } else {
            // We extract the line number from the id
            coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
        }
    }
    coverage.to_prev_chunk();
};

// Select line number lineno, or if it is in a colored chunk, select the
// entire chunk
coverage.select_line_or_chunk = function (lineno) {
    var c = coverage;
    var probe_line = c.line_elt(lineno);
    if (!probe_line) {
        return;
    }
    var the_indicator = c.chunk_indicator(probe_line);
    if (the_indicator) {
        // The line is in a highlighted chunk.
        // Search backward for the first line.
        var probe = lineno;
        var indicator = the_indicator;
        while (probe > 0 && indicator === the_indicator) {
            probe--;
            probe_line = c.line_elt(probe);
            if (!probe_line) {
                break;
            }
            indicator = c.chunk_indicator(probe_line);
        }
        var begin = probe + 1;

        // Search forward for the last line.
        probe = lineno;
        indicator = the_indicator;
        while (indicator === the_indicator) {
            probe++;
            probe_line = c.line_elt(probe);
            indicator = c.chunk_indicator(probe_line);
        }

        coverage.set_sel(begin, probe);
    }
    else {
        coverage.set_sel(lineno);
    }
};

coverage.show_selection = function () {
    // Highlight the lines in the chunk
    document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight"));
    for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) {
        coverage.line_elt(probe).querySelector(".n").classList.add("highlight");
    }

    coverage.scroll_to_selection();
};

coverage.scroll_to_selection = function () {
    // Scroll the page if the chunk isn't fully visible.
    if (coverage.selection_ends_on_screen() < 2) {
        const element = coverage.line_elt(coverage.sel_begin);
        coverage.scroll_window(element.offsetTop - 60);
    }
};

coverage.scroll_window = function (to_pos) {
    window.scroll({top: to_pos, behavior: "smooth"});
};

coverage.init_scroll_markers = function () {
    // Init some variables
    coverage.lines_len = document.querySelectorAll("#source > p").length;

    // Build html
    coverage.build_scroll_markers();
};

coverage.build_scroll_markers = function () {
    const temp_scroll_marker = document.getElementById("scroll_marker")
    if (temp_scroll_marker) temp_scroll_marker.remove();
    // Don't build markers if the window has no scroll bar.
    if (document.body.scrollHeight <= window.innerHeight) {
        return;
    }

    const marker_scale = window.innerHeight / document.body.scrollHeight;
    const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10);

    let previous_line = -99, last_mark, last_top;

    const scroll_marker = document.createElement("div");
    scroll_marker.id = "scroll_marker";
    document.getElementById("source").querySelectorAll(
        "p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par"
    ).forEach(element => {
        const line_top = Math.floor(element.offsetTop * marker_scale);
        const line_number = parseInt(element.querySelector(".n a").id.substr(1));

        if (line_number === previous_line + 1) {
            // If this solid missed block just make previous mark higher.
            last_mark.style.height = `${line_top + line_height - last_top}px`;
        } else {
            // Add colored line in scroll_marker block.
            last_mark = document.createElement("div");
            last_mark.id = `m${line_number}`;
            last_mark.classList.add("marker");
            last_mark.style.height = `${line_height}px`;
            last_mark.style.top = `${line_top}px`;
            scroll_marker.append(last_mark);
            last_top = line_top;
        }

        previous_line = line_number;
    });

    // Append last to prevent layout calculation
    document.body.append(scroll_marker);
};

coverage.wire_up_sticky_header = function () {
    const header = document.querySelector("header");
    const header_bottom = (
        header.querySelector(".content h2").getBoundingClientRect().top -
        header.getBoundingClientRect().top
    );

    function updateHeader() {
        if (window.scrollY > header_bottom) {
            header.classList.add("sticky");
        } else {
            header.classList.remove("sticky");
        }
    }

    window.addEventListener("scroll", updateHeader);
    updateHeader();
};

coverage.expand_contexts = function (e) {
    var ctxs = e.target.parentNode.querySelector(".ctxs");

    if (!ctxs.classList.contains("expanded")) {
        var ctxs_text = ctxs.textContent;
        var width = Number(ctxs_text[0]);
        ctxs.textContent = "";
        for (var i = 1; i < ctxs_text.length; i += width) {
            key = ctxs_text.substring(i, i + width).trim();
            ctxs.appendChild(document.createTextNode(contexts[key]));
            ctxs.appendChild(document.createElement("br"));
        }
        ctxs.classList.add("expanded");
    }
};

document.addEventListener("DOMContentLoaded", () => {
    if (document.body.classList.contains("indexfile")) {
        coverage.index_ready();
    } else {
        coverage.pyfile_ready();
    }
});


path: htmlcov/d_ce21df766c911d41_device_discovery_py.html
content: <!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Coverage for src\core\device_discovery.py: 100%</title>
    <link rel="icon" sizes="32x32" href="favicon_32.png">
    <link rel="stylesheet" href="style.css" type="text/css">
    <script type="text/javascript" src="coverage_html.js" defer></script>
</head>
<body class="pyfile">
<header>
    <div class="content">
        <h1>
            <span class="text">Coverage for </span><b>src\core\device_discovery.py</b>:
            <span class="pc_cov">100%</span>
        </h1>
        <aside id="help_panel_wrapper">
            <input id="help_panel_state" type="checkbox">
            <label for="help_panel_state">
                <img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
            </label>
            <div id="help_panel">
                <p class="legend">Shortcuts on this page</p>
                <div class="keyhelp">
                    <p>
                        <kbd>r</kbd>
                        <kbd>m</kbd>
                        <kbd>x</kbd>
                        &nbsp; toggle line displays
                    </p>
                    <p>
                        <kbd>j</kbd>
                        <kbd>k</kbd>
                        &nbsp; next/prev highlighted chunk
                    </p>
                    <p>
                        <kbd>0</kbd> &nbsp; (zero) top of page
                    </p>
                    <p>
                        <kbd>1</kbd> &nbsp; (one) first highlighted chunk
                    </p>
                    <p>
                        <kbd>[</kbd>
                        <kbd>]</kbd>
                        &nbsp; prev/next file
                    </p>
                    <p>
                        <kbd>u</kbd> &nbsp; up to the index
                    </p>
                    <p>
                        <kbd>?</kbd> &nbsp; show/hide this help
                    </p>
                </div>
            </div>
        </aside>
        <h2>
            <span class="text">0 statements &nbsp;</span>
            <button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">0<span class="text"> run</span></button>
            <button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">0<span class="text"> missing</span></button>
            <button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">0<span class="text"> excluded</span></button>
        </h2>
        <p class="text">
            <a id="prevFileLink" class="nav" href="d_ce21df766c911d41_device_py.html">&#xab; prev</a> &nbsp; &nbsp;
            <a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
            <a id="nextFileLink" class="nav" href="d_ce21df766c911d41_discovery_py.html">&#xbb; next</a>
            &nbsp; &nbsp; &nbsp;
            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.4">coverage.py v7.3.4</a>,
            created at 2026-04-16 16:31 +0800
        </p>
        <aside class="hidden">
            <button type="button" class="button_next_chunk" data-shortcut="j"/>
            <button type="button" class="button_prev_chunk" data-shortcut="k"/>
            <button type="button" class="button_top_of_page" data-shortcut="0"/>
            <button type="button" class="button_first_chunk" data-shortcut="1"/>
            <button type="button" class="button_prev_file" data-shortcut="["/>
            <button type="button" class="button_next_file" data-shortcut="]"/>
            <button type="button" class="button_to_index" data-shortcut="u"/>
            <button type="button" class="button_show_hide_help" data-shortcut="?"/>
        </aside>
    </div>
</header>
<main id="source">
</main>
<footer>
    <div class="content">
        <p>
            <a id="prevFileLink" class="nav" href="d_ce21df766c911d41_device_py.html">&#xab; prev</a> &nbsp; &nbsp;
            <a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
            <a id="nextFileLink" class="nav" href="d_ce21df766c911d41_discovery_py.html">&#xbb; next</a>
            &nbsp; &nbsp; &nbsp;
            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.4">coverage.py v7.3.4</a>,
            created at 2026-04-16 16:31 +0800
        </p>
    </div>
</footer>
</body>
</html>


path: htmlcov/d_ce21df766c911d41___init___py.html
content: <!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Coverage for src\core\__init__.py: 100%</title>
    <link rel="icon" sizes="32x32" href="favicon_32.png">
    <link rel="stylesheet" href="style.css" type="text/css">
    <script type="text/javascript" src="coverage_html.js" defer></script>
</head>
<body class="pyfile">
<header>
    <div class="content">
        <h1>
            <span class="text">Coverage for </span><b>src\core\__init__.py</b>:
            <span class="pc_cov">100%</span>
        </h1>
        <aside id="help_panel_wrapper">
            <input id="help_panel_state" type="checkbox">
            <label for="help_panel_state">
                <img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
            </label>
            <div id="help_panel">
                <p class="legend">Shortcuts on this page</p>
                <div class="keyhelp">
                    <p>
                        <kbd>r</kbd>
                        <kbd>m</kbd>
                        <kbd>x</kbd>
                        &nbsp; toggle line displays
                    </p>
                    <p>
                        <kbd>j</kbd>
                        <kbd>k</kbd>
                        &nbsp; next/prev highlighted chunk
                    </p>
                    <p>
                        <kbd>0</kbd> &nbsp; (zero) top of page
                    </p>
                    <p>
                        <kbd>1</kbd> &nbsp; (one) first highlighted chunk
                    </p>
                    <p>
                        <kbd>[</kbd>
                        <kbd>]</kbd>
                        &nbsp; prev/next file
                    </p>
                    <p>
                        <kbd>u</kbd> &nbsp; up to the index
                    </p>
                    <p>
                        <kbd>?</kbd> &nbsp; show/hide this help
                    </p>
                </div>
            </div>
        </aside>
        <h2>
            <span class="text">0 statements &nbsp;</span>
            <button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">0<span class="text"> run</span></button>
            <button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">0<span class="text"> missing</span></button>
            <button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">0<span class="text"> excluded</span></button>
        </h2>
        <p class="text">
            <a id="prevFileLink" class="nav" href="d_145eef247bfb46b6_context_awareness_py.html">&#xab; prev</a> &nbsp; &nbsp;
            <a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
            <a id="nextFileLink" class="nav" href="d_ce21df766c911d41_communication_py.html">&#xbb; next</a>
            &nbsp; &nbsp; &nbsp;
            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.4">coverage.py v7.3.4</a>,
            created at 2026-04-16 16:45 +0800
        </p>
        <aside class="hidden">
            <button type="button" class="button_next_chunk" data-shortcut="j"/>
            <button type="button" class="button_prev_chunk" data-shortcut="k"/>
            <button type="button" class="button_top_of_page" data-shortcut="0"/>
            <button type="button" class="button_first_chunk" data-shortcut="1"/>
            <button type="button" class="button_prev_file" data-shortcut="["/>
            <button type="button" class="button_next_file" data-shortcut="]"/>
            <button type="button" class="button_to_index" data-shortcut="u"/>
            <button type="button" class="button_show_hide_help" data-shortcut="?"/>
        </aside>
    </div>
</header>
<main id="source">
</main>
<footer>
    <div class="content">
        <p>
            <a id="prevFileLink" class="nav" href="d_145eef247bfb46b6_context_awareness_py.html">&#xab; prev</a> &nbsp; &nbsp;
            <a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
            <a id="nextFileLink" class="nav" href="d_ce21df766c911d41_communication_py.html">&#xbb; next</a>
            &nbsp; &nbsp; &nbsp;
            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.4">coverage.py v7.3.4</a>,
            created at 2026-04-16 16:45 +0800
        </p>
    </div>
</footer>
</body>
</html>


path: htmlcov/index.html
content: <!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Coverage report</title>
    <link rel="icon" sizes="32x32" href="favicon_32.png">
    <link rel="stylesheet" href="style.css" type="text/css">
    <script type="text/javascript" src="coverage_html.js" defer></script>
</head>
<body class="indexfile">
<header>
    <div class="content">
        <h1>Coverage report:
            <span class="pc_cov">3%</span>
        </h1>
        <aside id="help_panel_wrapper">
            <input id="help_panel_state" type="checkbox">
            <label for="help_panel_state">
                <img id="keyboard_icon" src="keybd_closed.png" alt="Show/hide keyboard shortcuts" />
            </label>
            <div id="help_panel">
                <p class="legend">Shortcuts on this page</p>
                <div class="keyhelp">
                    <p>
                        <kbd>n</kbd>
                        <kbd>s</kbd>
                        <kbd>m</kbd>
                        <kbd>x</kbd>
                        <kbd>c</kbd>
                        &nbsp; change column sorting
                    </p>
                    <p>
                        <kbd>[</kbd>
                        <kbd>]</kbd>
                        &nbsp; prev/next file
                    </p>
                    <p>
                        <kbd>?</kbd> &nbsp; show/hide this help
                    </p>
                </div>
            </div>
        </aside>
        <form id="filter_container">
            <input id="filter" type="text" value="" placeholder="filter..." />
        </form>
        <p class="text">
            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.4">coverage.py v7.3.4</a>,
            created at 2026-04-21 14:55 +0800
        </p>
    </div>
</header>
<main id="index">
    <table class="index" data-sortable>
        <thead>
            <tr class="tablehead" title="Click to sort">
                <th class="name left" aria-sort="none" data-shortcut="n">Module</th>
                <th aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements</th>
                <th aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing</th>
                <th aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded</th>
                <th class="right" aria-sort="none" data-shortcut="c">coverage</th>
            </tr>
        </thead>
        <tbody>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_context_awareness_py.html">src\context_awareness.py</a></td>
                <td>179</td>
                <td>179</td>
                <td>0</td>
                <td class="right" data-ratio="0 179">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41___init___py.html">src\core\__init__.py</a></td>
                <td>0</td>
                <td>0</td>
                <td>0</td>
                <td class="right" data-ratio="0 0">100%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_communication_py.html">src\core\communication.py</a></td>
                <td>80</td>
                <td>80</td>
                <td>0</td>
                <td class="right" data-ratio="0 80">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_config_py.html">src\core\config.py</a></td>
                <td>216</td>
                <td>216</td>
                <td>0</td>
                <td class="right" data-ratio="0 216">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_device_py.html">src\core\device.py</a></td>
                <td>128</td>
                <td>128</td>
                <td>0</td>
                <td class="right" data-ratio="0 128">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_device_discovery_py.html">src\core\device_discovery.py</a></td>
                <td>0</td>
                <td>0</td>
                <td>0</td>
                <td class="right" data-ratio="0 0">100%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_discovery_py.html">src\core\discovery.py</a></td>
                <td>227</td>
                <td>227</td>
                <td>0</td>
                <td class="right" data-ratio="0 227">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_heartbeat_py.html">src\core\heartbeat.py</a></td>
                <td>163</td>
                <td>74</td>
                <td>0</td>
                <td class="right" data-ratio="89 163">55%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_memory_py.html">src\core\memory.py</a></td>
                <td>215</td>
                <td>215</td>
                <td>0</td>
                <td class="right" data-ratio="0 215">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_storage_py.html">src\core\storage.py</a></td>
                <td>303</td>
                <td>303</td>
                <td>0</td>
                <td class="right" data-ratio="0 303">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_ce21df766c911d41_sync_py.html">src\core\sync.py</a></td>
                <td>258</td>
                <td>258</td>
                <td>0</td>
                <td class="right" data-ratio="0 258">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_learning_loop_py.html">src\learning_loop.py</a></td>
                <td>210</td>
                <td>210</td>
                <td>0</td>
                <td class="right" data-ratio="0 210">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_p2p_network_py.html">src\p2p_network.py</a></td>
                <td>203</td>
                <td>203</td>
                <td>0</td>
                <td class="right" data-ratio="0 203">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_performance_py.html">src\performance.py</a></td>
                <td>140</td>
                <td>140</td>
                <td>0</td>
                <td class="right" data-ratio="0 140">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_skill_manager_py.html">src\skill_manager.py</a></td>
                <td>222</td>
                <td>222</td>
                <td>0</td>
                <td class="right" data-ratio="0 222">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_sync_monitor_py.html">src\sync_monitor.py</a></td>
                <td>184</td>
                <td>184</td>
                <td>0</td>
                <td class="right" data-ratio="0 184">0%</td>
            </tr>
            <tr class="file">
                <td class="name left"><a href="d_145eef247bfb46b6_system_integrator_py.html">src\system_integrator.py</a></td>
                <td>181</td>
                <td>181</td>
                <td>0</td>
                <td class="right" data-ratio="0 181">0%</td>
            </tr>
        </tbody>
        <tfoot>
            <tr class="total">
                <td class="name left">Total</td>
                <td>2909</td>
                <td>2820</td>
                <td>0</td>
                <td class="right" data-ratio="89 2909">3%</td>
            </tr>
        </tfoot>
    </table>
    <p id="no_rows">
        No items found using the specified filter.
    </p>
</main>
<footer>
    <div class="content">
        <p>
            <a class="nav" href="https://coverage.readthedocs.io/en/7.3.4">coverage.py v7.3.4</a>,
            created at 2026-04-21 14:55 +0800
        </p>
    </div>
    <aside class="hidden">
        <a id="prevFileLink" class="nav" href="d_145eef247bfb46b6_system_integrator_py.html"/>
        <a id="nextFileLink" class="nav" href="d_145eef247bfb46b6_context_awareness_py.html"/>
        <button type="button" class="button_prev_file" data-shortcut="["/>
        <button type="button" class="button_next_file" data-shortcut="]"/>
        <button type="button" class="button_show_hide_help" data-shortcut="?"/>
    </aside>
</footer>
</body>
</html>


path: htmlcov/status.json
content: {"format":2,"version":"7.3.4","globals":"39b3e6730d9c70813aa0f834d81672ec","files":{"d_145eef247bfb46b6_context_awareness_py":{"hash":"45d54760ef9470bffd20a2213feacdcf","index":{"nums":[0,1,179,0,179,0,0,0],"html_filename":"d_145eef247bfb46b6_context_awareness_py.html","relative_filename":"src\\context_awareness.py"}},"d_ce21df766c911d41___init___py":{"hash":"e6baa73cda2916dad605215f937a92e1","index":{"nums":[0,1,0,0,0,0,0,0],"html_filename":"d_ce21df766c911d41___init___py.html","relative_filename":"src\\core\\__init__.py"}},"d_ce21df766c911d41_communication_py":{"hash":"aa9d3ec17cba154a82a496217138028f","index":{"nums":[0,1,80,0,80,0,0,0],"html_filename":"d_ce21df766c911d41_communication_py.html","relative_filename":"src\\core\\communication.py"}},"d_ce21df766c911d41_config_py":{"hash":"7e046b454841f2931e7d2de56109f905","index":{"nums":[0,1,216,0,216,0,0,0],"html_filename":"d_ce21df766c911d41_config_py.html","relative_filename":"src\\core\\config.py"}},"d_ce21df766c911d41_device_py":{"hash":"99463f061cee1e1f8c6b2b0edf8e19b0","index":{"nums":[0,1,128,0,128,0,0,0],"html_filename":"d_ce21df766c911d41_device_py.html","relative_filename":"src\\core\\device.py"}},"d_ce21df766c911d41_device_discovery_py":{"hash":"8f96e0487d400bed7b4c91e861310773","index":{"nums":[0,1,0,0,0,0,0,0],"html_filename":"d_ce21df766c911d41_device_discovery_py.html","relative_filename":"src\\core\\device_discovery.py"}},"d_ce21df766c911d41_discovery_py":{"hash":"682079c8f127f1e335bd3a66a84763f3","index":{"nums":[0,1,227,0,227,0,0,0],"html_filename":"d_ce21df766c911d41_discovery_py.html","relative_filename":"src\\core\\discovery.py"}},"d_ce21df766c911d41_memory_py":{"hash":"3ebfb71fac414ddee50bc2b0d1393e8c","index":{"nums":[0,1,215,0,215,0,0,0],"html_filename":"d_ce21df766c911d41_memory_py.html","relative_filename":"src\\core\\memory.py"}},"d_ce21df766c911d41_storage_py":{"hash":"8adf6b0a53349ac8699746a0368a767e","index":{"nums":[0,1,303,0,303,0,0,0],"html_filename":"d_ce21df766c911d41_storage_py.html","relative_filename":"src\\core\\storage.py"}},"d_ce21df766c911d41_sync_py":{"hash":"4fd8aeefc1c14e36db5e43188973f0b2","index":{"nums":[0,1,258,0,258,0,0,0],"html_filename":"d_ce21df766c911d41_sync_py.html","relative_filename":"src\\core\\sync.py"}},"d_145eef247bfb46b6_learning_loop_py":{"hash":"25eb8eac0c5d1ef62ca76b6ad460e9d0","index":{"nums":[0,1,210,0,210,0,0,0],"html_filename":"d_145eef247bfb46b6_learning_loop_py.html","relative_filename":"src\\learning_loop.py"}},"d_145eef247bfb46b6_p2p_network_py":{"hash":"ff020bc9037af263cf1f60df69668fb0","index":{"nums":[0,1,203,0,203,0,0,0],"html_filename":"d_145eef247bfb46b6_p2p_network_py.html","relative_filename":"src\\p2p_network.py"}},"d_145eef247bfb46b6_skill_manager_py":{"hash":"71b9c346adeb847dc104fb2cd2ad65cb","index":{"nums":[0,1,222,0,222,0,0,0],"html_filename":"d_145eef247bfb46b6_skill_manager_py.html","relative_filename":"src\\skill_manager.py"}},"d_145eef247bfb46b6_sync_monitor_py":{"hash":"28a8836a3edddf5ba3ec4f647c77f341","index":{"nums":[0,1,184,0,184,0,0,0],"html_filename":"d_145eef247bfb46b6_sync_monitor_py.html","relative_filename":"src\\sync_monitor.py"}},"d_145eef247bfb46b6_system_integrator_py":{"hash":"e8d8b4af327ff88ad9461cad49173c20","index":{"nums":[0,1,181,0,181,0,0,0],"html_filename":"d_145eef247bfb46b6_system_integrator_py.html","relative_filename":"src\\system_integrator.py"}},"d_ce21df766c911d41_heartbeat_py":{"hash":"12f1d8c8ed07e248f2db8c208fcfaa80","index":{"nums":[0,1,163,0,74,0,0,0],"html_filename":"d_ce21df766c911d41_heartbeat_py.html","relative_filename":"src\\core\\heartbeat.py"}},"d_145eef247bfb46b6_performance_py":{"hash":"5003bfecd68f65b7fb08f14ccd623365","index":{"nums":[0,1,140,0,140,0,0,0],"html_filename":"d_145eef247bfb46b6_performance_py.html","relative_filename":"src\\performance.py"}}}}

path: htmlcov/style.css
content: @charset "UTF-8";
/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
/* Don't edit this .css file. Edit the .scss file instead! */
html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }

body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; }

@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } }

@media (prefers-color-scheme: dark) { body { color: #eee; } }

html > body { font-size: 16px; }

a:active, a:focus { outline: 2px dashed #007acc; }

p { font-size: .875em; line-height: 1.4em; }

table { border-collapse: collapse; }

td { vertical-align: top; }

table tr.hidden { display: none !important; }

p#no_rows { display: none; font-size: 1.2em; }

a.nav { text-decoration: none; color: inherit; }

a.nav:hover { text-decoration: underline; color: inherit; }

.hidden { display: none; }

header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; }

@media (prefers-color-scheme: dark) { header { background: black; } }

@media (prefers-color-scheme: dark) { header { border-color: #333; } }

header .content { padding: 1rem 3.5rem; }

header h2 { margin-top: .5em; font-size: 1em; }

header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; }

@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } }

header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; }

header.sticky .text { display: none; }

header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; }

header.sticky .content { padding: 0.5rem 3.5rem; }

header.sticky .content p { font-size: 1em; }

header.sticky ~ #source { padding-top: 6.5em; }

main { position: relative; z-index: 1; }

footer { margin: 1rem 3.5rem; }

footer .content { padding: 0; color: #666; font-style: italic; }

@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } }

#index { margin: 1rem 0 0 3.5rem; }

h1 { font-size: 1.25em; display: inline-block; }

#filter_container { float: right; margin: 0 2em 0 0; }

#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; }

@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } }

@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } }

@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } }

#filter_container input:focus { border-color: #007acc; }

header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; }

@media (prefers-color-scheme: dark) { header button { border-color: #444; } }

header button:active, header button:focus { outline: 2px dashed #007acc; }

header button.run { background: #eeffee; }

@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } }

header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; }

@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } }

header button.mis { background: #ffeeee; }

@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } }

header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; }

@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } }

header button.exc { background: #f7f7f7; }

@media (prefers-color-scheme: dark) { header button.exc { background: #333; } }

header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; }

@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } }

header button.par { background: #ffffd5; }

@media (prefers-color-scheme: dark) { header button.par { background: #650; } }

header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; }

@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } }

#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }

#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }

#help_panel_wrapper { float: right; position: relative; }

#keyboard_icon { margin: 5px; }

#help_panel_state { display: none; }

#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; }

#help_panel .keyhelp p { margin-top: .75em; }

#help_panel .legend { font-style: italic; margin-bottom: 1em; }

.indexfile #help_panel { width: 25em; }

.pyfile #help_panel { width: 18em; }

#help_panel_state:checked ~ #help_panel { display: block; }

kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; }

#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; }

#source p { position: relative; white-space: pre; }

#source p * { box-sizing: border-box; }

#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; }

@media (prefers-color-scheme: dark) { #source p .n { color: #777; } }

#source p .n.highlight { background: #ffdd00; }

#source p .n a { margin-top: -4em; padding-top: 4em; text-decoration: none; color: #999; }

@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } }

#source p .n a:hover { text-decoration: underline; color: #999; }

@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } }

#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; }

@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } }

#source p .t:hover { background: #f2f2f2; }

@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } }

#source p .t:hover ~ .r .annotate.long { display: block; }

#source p .t .com { color: #008000; font-style: italic; line-height: 1px; }

@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } }

#source p .t .key { font-weight: bold; line-height: 1px; }

#source p .t .str { color: #0451a5; }

@media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } }

#source p.mis .t { border-left: 0.2em solid #ff0000; }

#source p.mis.show_mis .t { background: #fdd; }

@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } }

#source p.mis.show_mis .t:hover { background: #f2d2d2; }

@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } }

#source p.run .t { border-left: 0.2em solid #00dd00; }

#source p.run.show_run .t { background: #dfd; }

@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } }

#source p.run.show_run .t:hover { background: #d2f2d2; }

@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } }

#source p.exc .t { border-left: 0.2em solid #808080; }

#source p.exc.show_exc .t { background: #eee; }

@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } }

#source p.exc.show_exc .t:hover { background: #e2e2e2; }

@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } }

#source p.par .t { border-left: 0.2em solid #bbbb00; }

#source p.par.show_par .t { background: #ffa; }

@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } }

#source p.par.show_par .t:hover { background: #f2f2a2; }

@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } }

#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; }

#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; }

@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } }

#source p .annotate.short:hover ~ .long { display: block; }

#source p .annotate.long { width: 30em; right: 2.5em; }

#source p input { display: none; }

#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; }

#source p input ~ .r label.ctx::before { content: "▶ "; }

#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; }

@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } }

@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } }

#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; }

@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } }

@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } }

#source p input:checked ~ .r label.ctx::before { content: "▼ "; }

#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; }

#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; }

@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } }

#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; }

@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } }

#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; }

#index table.index { margin-left: -.5em; }

#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; }

@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } }

#index td.name, #index th.name { text-align: left; width: auto; }

#index th { font-style: italic; color: #333; cursor: pointer; }

@media (prefers-color-scheme: dark) { #index th { color: #ddd; } }

#index th:hover { background: #eee; }

@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } }

#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; }

@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } }

#index th[aria-sort="ascending"]::after { font-family: sans-serif; content: " ↑"; }

#index th[aria-sort="descending"]::after { font-family: sans-serif; content: " ↓"; }

#index td.name a { text-decoration: none; color: inherit; }

#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; }

#index tr.file:hover { background: #eee; }

@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } }

#index tr.file:hover td.name { text-decoration: underline; color: inherit; }

#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; }

@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } }

@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } }

#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; }

@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } }


path: pyproject.toml
content: [build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "pao-system"
version = "1.1.2"
description = "Personal AI Operating System - Cross-device AI coordination platform"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "MIT"}
dynamic = ["scripts", "dependencies"]
authors = [
    {name = "PAO Project Team", email = "[REDACTED_SECRET]"}
]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: End Users/Desktop",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Communications",
    "Topic :: Database",
    "Topic :: System :: Distributed Computing",
    "Topic :: System :: Networking",
]

[project.urls]
Homepage = "https://github.com/pao-system/pao"
Documentation = "https://docs.pao-system.dev"

[tool.black]
line-length = 88
target-version = ['py39']

[tool.isort]
profile = "black"
line_length = 88

[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-cov>=4.0",
    "black>=24.1.1",
    "flake8>=6.0.0",
    "mypy>=1.8.0",
]

ui = [
    "streamlit>=1.29.0",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-v --cov=src --cov-report=term-missing --cov-report=html"

path: requirements.txt
content: # PAO 系统核心依赖
# 第一阶段：设备发现与基础通信
# 第二阶段：本地记忆系统与基本同步

zeroconf==0.38.4          # 设备发现服务
websockets==11.0.3        # WebSocket 实时通信
cryptography==41.0.7      # 加密和安全
pydantic==2.5.0          # 数据验证和序列化
python-dotenv==1.0.0      # 环境配置
aiofiles==23.2.1          # 异步文件操作（第二阶段新增）
aiohttp==3.9.3            # HTTP 客户端（测试依赖）

# 开发和测试
pytest==7.4.4
pytest-asyncio==0.21.1
black==24.1.1
flake8==6.1.0

# 日志和监控
loguru==0.7.2
prometheus-client==0.19.0

# 性能监控
psutil==5.9.8            # 系统资源监控（性能测试依赖）

path: run_quick_tests.py
content: """Quick Test Suite"""
import sys
sys.path.insert(0, 'src')
import asyncio
from src.system_integrator import PAOSystemIntegrator
from src.learning_loop import FeedbackType

async def quick_test():
    print('Quick Test Suite')
    print('=' * 40)

    # Test 1: System Init
    print('[1] System Init')
    system = PAOSystemIntegrator()
    await system.initialize()
    print('    Device ID:', system.status.device_id)
    print('    Skills:', system.status.skills_loaded)
    print('    Memories:', system.status.memory_items)

    # Test 2: Skill Search
    print('[2] Skill Search')
    skills = await system.search_skills('python')
    print('    Found:', len(skills), 'skills')

    # Test 3: Memory Store
    print('[3] Memory Store')
    memory_id = await system.store_memory('test', {'data': 'test'}, priority=3)
    print('    Stored:', memory_id[:8] + '...')

    # Test 4: Memory Retrieve
    print('[4] Memory Retrieve')
    memories = await system.retrieve_memories('test')
    print('    Retrieved:', len(memories), 'items')

    # Test 5: Learning Loop
    print('[5] Learning Loop')
    await system.learning_loop.submit_feedback(FeedbackType.EXPLICIT, 'test', {}, 8.0, 'good')
    status = await system.learning_loop.get_learning_status()
    print('    Feedbacks:', status['total_feedbacks'])

    print('=' * 40)
    print('All Tests Passed!')

asyncio.run(quick_test())


path: src/pao_system.egg-info/dependency_links.txt
content: 


path: src/pao_system.egg-info/entry_points.txt
content: [console_scripts]
pao = pao:main


path: src/pao_system.egg-info/requires.txt
content: zeroconf==0.38.4
websockets==11.0.3
cryptography==41.0.7
pydantic==2.5.0
python-dotenv==1.0.0
aiofiles==23.2.1
pytest==7.4.4
pytest-asyncio==0.21.1
black==24.1.1
flake8==6.1.0
loguru==0.7.2
prometheus-client==0.19.0

[dev]
pytest>=7.0
pytest-cov>=4.0
black>=24.1.1
flake8>=6.0.0
mypy>=1.8.0

[ui]
streamlit>=1.29.0


path: src/pao_system.egg-info/top_level.txt
content: core


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)