"""PostgreSQL 渠道网关模型 - 渠道绑定、出站重试、消息审计日志、插件注册""" from sqlalchemy import ( JSON, Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, ) from yuxi.storage.postgres.models_business import Base from yuxi.utils.datetime_utils import format_utc_datetime, utc_now_naive class ChannelBinding(Base): """渠道 Agent 绑定路由表 — Worker Step 3 查询,决定消息路由到哪个 Agent""" __tablename__ = "channel_bindings" id = Column(Integer, primary_key=True, autoincrement=True, comment="主键") channel_type = Column(String(20), nullable=False, index=True, comment="渠道类型: feishu/dingtalk/hooks") account_id = Column(String(64), nullable=False, default="", comment="渠道账户标识") group_id = Column(String(128), nullable=False, default="", comment="群组标识,DM 为空字符串") agent_config_id = Column(Integer, ForeignKey("agent_configs.id"), nullable=False, comment="绑定的 Agent 配置 ID") session_key_strategy = Column(String(20), nullable=False, default="auto", comment="会话键策略: auto/main/channel_group/custom") is_enabled = Column(Boolean, nullable=False, default=True, index=True, comment="是否启用") created_by = Column(String(64), nullable=True) updated_by = Column(String(64), nullable=True) created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间") updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间") is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是") deleted_at = Column(DateTime, nullable=True, comment="删除时间") __table_args__ = ( UniqueConstraint("channel_type", "account_id", "group_id", name="uq_channel_binding"), ) def to_dict(self): return { "id": self.id, "channel_type": self.channel_type, "account_id": self.account_id, "group_id": self.group_id, "agent_config_id": self.agent_config_id, "session_key_strategy": self.session_key_strategy, "is_enabled": bool(self.is_enabled), "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), "updated_at": format_utc_datetime(self.updated_at), "is_deleted": self.is_deleted, "deleted_at": format_utc_datetime(self.deleted_at), } class ChannelOutbox(Base): """出站消息重试队列 — 适配器发送失败后持久化,OutboxRetryWorker 按指数退避重试""" __tablename__ = "channel_outbox" id = Column(Integer, primary_key=True, autoincrement=True, comment="主键") message_id = Column(String(64), nullable=False, index=True, comment="关联的原始消息 ID") session_id = Column(String(64), nullable=False, index=True, comment="会话 ID (thread_id)") channel_type = Column(String(20), nullable=False, index=True, comment="目标渠道类型") content = Column(Text, nullable=False, comment="待发送内容 (标准 Markdown)") status = Column( String(20), nullable=False, default="pending", index=True, comment="状态: pending/retrying/sent/failed/dead", ) retry_count = Column(Integer, nullable=False, default=0, comment="已重试次数") max_retries = Column(Integer, nullable=False, default=5, comment="最大重试次数") next_retry_at = Column(DateTime, nullable=True, index=True, comment="下次重试时间") last_error = Column(Text, nullable=True, comment="最后一次发送错误信息") trace_id = Column(String(64), nullable=True, index=True, comment="链路追踪 ID") extra_metadata = Column(JSON, nullable=True, comment="扩展元数据") created_by = Column(String(64), nullable=True) updated_by = Column(String(64), nullable=True) created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间") updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间") is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是") deleted_at = Column(DateTime, nullable=True, comment="删除时间") def to_dict(self): return { "id": self.id, "message_id": self.message_id, "session_id": self.session_id, "channel_type": self.channel_type, "content": self.content, "status": self.status, "retry_count": self.retry_count, "max_retries": self.max_retries, "next_retry_at": format_utc_datetime(self.next_retry_at), "last_error": self.last_error, "trace_id": self.trace_id, "extra_metadata": self.extra_metadata or {}, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), "updated_at": format_utc_datetime(self.updated_at), "is_deleted": self.is_deleted, "deleted_at": format_utc_datetime(self.deleted_at), } class ChannelMessageLog(Base): """渠道消息审计日志 — 记录入站/出站消息处理轨迹,支持 trace_id 链路追踪""" __tablename__ = "channel_message_logs" id = Column(Integer, primary_key=True, autoincrement=True, comment="主键") trace_id = Column(String(64), nullable=False, index=True, comment="链路追踪 ID") message_id = Column(String(64), nullable=False, index=True, comment="消息 ID") channel_type = Column(String(20), nullable=False, index=True, comment="渠道类型") conversation_id = Column(Integer, nullable=True, index=True, comment="关联的 Conversation ID") session_id = Column(String(64), nullable=True, index=True, comment="会话 ID") direction = Column(String(10), nullable=False, index=True, comment="方向: inbound/outbound") sender_id = Column(String(64), nullable=True, comment="发送者 ID") content_summary = Column(String(500), nullable=True, comment="内容摘要(前 500 字符)") agent_config_id = Column(Integer, nullable=True, index=True, comment="Agent 配置 ID") status = Column(String(20), nullable=False, default="received", index=True, comment="处理状态") pipeline_result = Column(String(20), nullable=True, comment="管道处理结果: accepted/aborted/skipped") abort_reason = Column(String(64), nullable=True, comment="管道中止原因码") worker_result = Column(String(20), nullable=True, comment="Worker 处理结果") error_message = Column(Text, nullable=True, comment="错误信息") processing_time_ms = Column(Integer, nullable=True, comment="总处理耗时(毫秒)") extra_metadata = Column(JSON, nullable=True, comment="扩展元数据") created_by = Column(String(64), nullable=True) updated_by = Column(String(64), nullable=True) created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间") updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间") is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是") deleted_at = Column(DateTime, nullable=True, comment="删除时间") def to_dict(self): return { "id": self.id, "trace_id": self.trace_id, "message_id": self.message_id, "channel_type": self.channel_type, "conversation_id": self.conversation_id, "session_id": self.session_id, "direction": self.direction, "sender_id": self.sender_id, "content_summary": self.content_summary, "agent_config_id": self.agent_config_id, "status": self.status, "pipeline_result": self.pipeline_result, "abort_reason": self.abort_reason, "worker_result": self.worker_result, "error_message": self.error_message, "processing_time_ms": self.processing_time_ms, "extra_metadata": self.extra_metadata or {}, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), "updated_at": format_utc_datetime(self.updated_at), "is_deleted": self.is_deleted, "deleted_at": format_utc_datetime(self.deleted_at), } class ChannelPlugin(Base): """渠道插件注册表 — 存储插件元数据、状态与配置 对应领域模型:PluginRecord 实体 覆盖来源:workspace(本地开发) + installed(第三方安装) bundled(内置) 插件不入库,由代码自动注册 """ __tablename__ = "channel_plugins" id = Column(Integer, primary_key=True, autoincrement=True, comment="主键") plugin_id = Column(String(64), nullable=False, unique=True, index=True, comment="插件唯一标识") name = Column(String(128), nullable=False, comment="插件显示名称") version = Column(String(32), nullable=False, default="0.0.0", comment="插件版本号") channel_type = Column(String(64), nullable=False, index=True, comment="渠道类型标识") origin = Column(String(32), nullable=False, comment="来源: bundled/workspace/installed") status = Column(String(32), nullable=False, default="discovered", comment="状态: discovered/registered/configured/active/error/disabled") root_dir = Column(String(512), nullable=True, comment="插件根目录路径") entry_file = Column(String(512), nullable=True, comment="入口文件路径") manifest = Column(JSON, nullable=True, comment="完整插件清单(yuxi.plugin.yaml内容)") capabilities = Column(JSON, nullable=True, comment="渠道能力声明") config_schema = Column(JSON, nullable=True, comment="配置校验schema") config = Column(JSON, nullable=True, default=dict, comment="当前生效配置") error = Column(Text, nullable=True, comment="错误信息") is_enabled = Column(Boolean, nullable=False, default=True, index=True, comment="是否启用") registered_at = Column(DateTime, nullable=True, comment="注册时间") activated_at = Column(DateTime, nullable=True, comment="最后激活时间") created_by = Column(String(64), nullable=True) updated_by = Column(String(64), nullable=True) created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间") updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间") is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是") deleted_at = Column(DateTime, nullable=True, comment="删除时间") def to_dict(self): return { "id": self.id, "plugin_id": self.plugin_id, "name": self.name, "version": self.version, "channel_type": self.channel_type, "origin": self.origin, "status": self.status, "root_dir": self.root_dir, "entry_file": self.entry_file, "manifest": self.manifest or {}, "capabilities": self.capabilities or {}, "config_schema": self.config_schema or {}, "config": self.config or {}, "error": self.error, "is_enabled": bool(self.is_enabled), "registered_at": format_utc_datetime(self.registered_at), "activated_at": format_utc_datetime(self.activated_at), "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), "updated_at": format_utc_datetime(self.updated_at), "is_deleted": self.is_deleted, "deleted_at": format_utc_datetime(self.deleted_at), } class ChannelPluginConfigHistory(Base): """插件配置历史表 — 记录配置变更,支持回滚 对应领域事件:PluginConfiguredEvent 生命周期:每次调用 set_config() 时写入一条记录 """ __tablename__ = "channel_plugin_config_history" id = Column(Integer, primary_key=True, autoincrement=True, comment="主键") plugin_id = Column(String(64), nullable=False, index=True, comment="插件标识") config = Column(JSON, nullable=False, comment="配置快照") changed_by = Column(String(128), nullable=True, comment="变更人") change_reason = Column(String(255), nullable=True, comment="变更原因") created_by = Column(String(64), nullable=True) updated_by = Column(String(64), nullable=True) created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间") updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间") is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是") deleted_at = Column(DateTime, nullable=True, comment="删除时间") def to_dict(self): return { "id": self.id, "plugin_id": self.plugin_id, "config": self.config or {}, "changed_by": self.changed_by, "change_reason": self.change_reason, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), "updated_at": format_utc_datetime(self.updated_at), "is_deleted": self.is_deleted, "deleted_at": format_utc_datetime(self.deleted_at), } class ChannelPluginAuditLog(Base): """插件操作审计日志 — 记录插件生命周期关键操作 对应领域事件:PluginRegisteredEvent / PluginActivatedEvent / PluginDeactivatedEvent / PluginReloadEvent """ __tablename__ = "channel_plugin_audit_logs" id = Column(Integer, primary_key=True, autoincrement=True, comment="主键") plugin_id = Column(String(64), nullable=False, index=True, comment="插件标识") action = Column(String(32), nullable=False, comment="操作: register/activate/deactivate/reload/error") old_status = Column(String(32), nullable=True, comment="操作前状态") new_status = Column(String(32), nullable=True, comment="操作后状态") old_version = Column(String(32), nullable=True, comment="操作前版本(重载场景)") new_version = Column(String(32), nullable=True, comment="操作后版本(重载场景)") detail = Column(JSON, nullable=True, comment="操作详情") error_message = Column(Text, nullable=True, comment="错误信息") created_by = Column(String(64), nullable=True) updated_by = Column(String(64), nullable=True) created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间") updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间") is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是") deleted_at = Column(DateTime, nullable=True, comment="删除时间") def to_dict(self): return { "id": self.id, "plugin_id": self.plugin_id, "action": self.action, "old_status": self.old_status, "new_status": self.new_status, "old_version": self.old_version, "new_version": self.new_version, "detail": self.detail or {}, "error_message": self.error_message, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), "updated_at": format_utc_datetime(self.updated_at), "is_deleted": self.is_deleted, "deleted_at": format_utc_datetime(self.deleted_at), }