"""配置 schema 元数据契约层定义。 定义配置字段的完整 schema 元数据(``CONFIG_SCHEMA``),包括键、类型、 是否必填、默认值、是否可热更新与约束,作为配置 schema 的唯一真相源 (§13.1 配置分层 / §6.1 契约层职责)。适配器(如 ``RedisConfigAdapter``) 与应用层(如 ``ConfigManager``)共享引用本模块,消除 schema 元数据 下沉至适配器层的 DRY 违反。 ``HOT_RELOADABLE_KEYS`` / ``NON_HOT_RELOADABLE_KEYS`` 从 ``CONFIG_SCHEMA`` 派生,供 ``ConfigManager`` 做热更新策略校验(ADP-030)。 """ from __future__ import annotations from yuxi.channels.contract.dtos.config import ConfigField, ConfigScope # 配置 schema:全部配置字段的完整元数据(FR-37 配置热更新)。 # 48 个可热更新字段 + 14 个不可热更新字段,共 62 个。 # 字段顺序:先可热更新(hot_reloadable=True),后不可热更新(hot_reloadable=False)。 CONFIG_SCHEMA: tuple[ConfigField, ...] = ( # === 可热更新字段(hot_reloadable=True)=== ConfigField( key="dm_policy", type="json", required=False, default="allow", hot_reloadable=True, title="私信策略", description="控制私信/群聊的自动回复与路由策略,如是否允许陌生人私信、是否自动创建会话等。", category="消息策略", ), ConfigField( key="allow_from", type="json", required=False, default=None, hot_reloadable=True, title="来源白名单", description="允许向当前账户发起对话的对端白名单,未配置时不限制。", category="访问控制", ), ConfigField( key="rate_limit", type="json", required=False, default=None, hot_reloadable=True, title="消息限流", description="控制账户级别的消息发送频率限制,防止滥用或触发渠道风控。", category="限流与预算", ), ConfigField( key="bot_loop_budget", type="json", required=False, default={"max_replies_per_hour": 20, "cooldown_seconds": 60}, hot_reloadable=True, title="Bot 循环预算", description=( "Bot 循环预算配置,包含 max_replies_per_hour" "(每小时最大回复数,≤0 表示不限制)和 cooldown_seconds(冷却秒数)。" ), category="限流与预算", ), # FR-08~14 功能开关(Task 30) ConfigField( key="rich_message_enabled", type="bool", required=False, default=False, hot_reloadable=True, title="富消息开关", description="是否启用富媒体消息(卡片、按钮等)发送能力。", category="功能开关", ), ConfigField( key="status_writeback_enabled", type="bool", required=False, default=False, hot_reloadable=True, title="状态回写开关", description="是否将渠道用户状态(在线/离开等)回写到知识库。", category="功能开关", ), ConfigField( key="agent_prompt_enabled", type="bool", required=False, default=False, hot_reloadable=True, title="Agent 提示开关", description="是否在消息中附加 Agent 提示上下文。", category="功能开关", ), ConfigField( key="channel_tools_enabled", type="bool", required=False, default=False, hot_reloadable=True, title="渠道工具开关", description="是否允许 Agent 调用渠道相关工具。", category="功能开关", ), # AC-22: 消息操作开关默认"开"(FR-12) ConfigField( key="message_ops_enabled", type="bool", required=False, default=True, hot_reloadable=True, title="消息操作开关", description="总开关:是否启用消息操作(反应、置顶、卡片更新等)。", category="功能开关", ), ConfigField( key="streaming_enabled", type="bool", required=False, default=False, hot_reloadable=True, title="流式响应开关", description="是否启用流式(打字机效果)响应输出。", category="功能开关", ), ConfigField( key="enable_typing", type="bool", required=False, default=True, hot_reloadable=True, title="输入中提示开关", description="是否向用户展示'正在输入'状态提示。", category="功能开关", ), ConfigField( key="directory_enabled", type="bool", required=False, default=True, hot_reloadable=True, title="目录服务开关", description="是否启用渠道目录(用户/群组列表)查询能力。", category="功能开关", ), ConfigField( key="command_enabled", type="bool", required=False, default=True, hot_reloadable=True, title="命令处理开关", description="是否启用以 command_prefix 开头的命令解析与处理。", category="功能开关", ), # FR-12 消息操作细粒度开关(PRD §FR-12 配置项)。 # AC-22 默认行为:表情反应"开"、置顶"关"、卡片更新"关"。 ConfigField( key="enable_reaction", type="bool", required=False, default=True, hot_reloadable=True, title="表情反应开关", description="是否允许对消息添加表情反应。", category="功能开关", ), ConfigField( key="enable_pin", type="bool", required=False, default=False, hot_reloadable=True, title="消息置顶开关", description="是否允许置顶/取消置顶消息。", category="功能开关", ), ConfigField( key="enable_card_update", type="bool", required=False, default=False, hot_reloadable=True, title="卡片更新开关", description="是否允许更新已发送的卡片消息。", category="功能开关", ), # FR-15 管理员命令权限白名单(账户级,JSON 数组,存放允许执行 ADMIN 权限命令的 peer_id) ConfigField( key="admin_users", type="json", required=False, default=None, hot_reloadable=True, scope=ConfigScope.ACCOUNT, title="管理员白名单", description="允许执行 ADMIN 权限命令的 peer_id 列表,账户级配置。", category="身份与权限", ), # FR-29 入站提及策略(账户级,JSON,含 allowed_implicit_kinds 列表, # 控制哪些隐式提及类型被视为"已提及",未配置时默认空策略) ConfigField( key="mention_policy", type="json", required=False, default=None, hot_reloadable=True, scope=ConfigScope.ACCOUNT, title="提及策略", description="控制哪些隐式提及类型被视为'已提及',影响命令触发与会话路由。", category="消息策略", ), # FR-07 会话合并策略开关(默认禁用) ConfigField( key="merge_strategy_enabled", type="bool", required=False, default=False, hot_reloadable=True, title="会话合并开关", description="是否启用按策略自动合并会话。", category="会话策略", ), # FR-06 跨渠道身份策略(默认隔离,可选 association 开启跨渠道关联) ConfigField( key="cross_channel_identity_strategy", type="str", required=False, default="isolation", hot_reloadable=True, title="跨渠道身份策略", description="跨渠道身份关联策略:isolation(隔离)或 association(关联)。", category="身份与权限", ), # 可配置值(Task 30) ConfigField( key="command_prefix", type="str", required=False, default="/", hot_reloadable=True, title="命令前缀", description="命令触发前缀,默认为 '/'。", category="命令与交互", ), ConfigField( key="fence_ttl_seconds", type="int", required=False, default=120, hot_reloadable=True, title="围栏 TTL", description="对话围栏(fence)的默认过期时间(秒)。", category="命令与交互", ), ConfigField( key="streaming_min_chunk_interval_ms", type="int", required=False, default=200, hot_reloadable=True, title="流式最小间隔", description="流式响应相邻分块之间的最小间隔(毫秒)。", category="流式与传输体验", ), ConfigField( key="streaming_ttl_seconds", type="int", required=False, default=60, hot_reloadable=True, title="流式 TTL", description="流式响应状态的最大保留时间(秒)。", category="流式与传输体验", ), ConfigField( key="typing_ttl_ms", type="int", required=False, default=10000, hot_reloadable=True, title="输入提示 TTL", description="'正在输入'提示状态的最大持续时间(毫秒)。", category="流式与传输体验", ), ConfigField( key="durability_policy_default", type="str", required=False, default="required", hot_reloadable=True, title="默认持久化策略", description="消息默认持久化策略:required / best_effort / none。", category="可靠性", ), # FR-24 分阶段 ACK 策略(PRD §FR-24)。 # 四阶段策略:after_record / after_agent_dispatch / after_persist / manual, # 默认 after_agent_dispatch(平衡可靠性与响应速度)。 ConfigField( key="ack_policy", type="str", required=False, default="after_agent_dispatch", hot_reloadable=True, title="ACK 策略", description="分阶段 ACK 策略:after_record / after_agent_dispatch / after_persist / manual。", category="可靠性", ), # 手动策略下插件忘记 ACK 时核心兜底超时(默认 60s)自动 ACK。 ConfigField( key="ack_fallback_timeout_seconds", type="int", required=False, default=60, hot_reloadable=True, title="ACK 兜底超时", description="手动 ACK 策略下,插件忘记 ACK 时核心自动 ACK 的超时时间(秒)。", category="可靠性", ), # 传输引擎配置 ConfigField( key="transport.stall_timeout_ms", type="int", required=False, default=120000, hot_reloadable=True, title="传输停滞超时", description="传输流判定为停滞的超时时间(毫秒)。", category="传输引擎", ), ConfigField( key="transport.backoff_schedule", type="str", required=False, default="1,2,5,10,30", hot_reloadable=True, title="传输退避序列", description="传输重试的退避时间序列,逗号分隔的秒数列表。", category="传输引擎", ), ConfigField( key="transport.backoff_jitter", type="float", required=False, default=0.2, hot_reloadable=True, title="传输退避抖动", description="传输重试退避的随机抖动系数。", category="传输引擎", ), # StreamWorker 断线重连指数退避参数(Task 10.3),与 TransportConfig # DTO 字段一一对应,由 base_worker._loadConfig 通过 ConfigPort 读取。 ConfigField( key="transport.stream_reconnect_backoff_ms", type="int", required=False, default=1000, hot_reloadable=True, title="流重连初始退避", description="StreamWorker 断线重连的指数退避基数(毫秒),每次重连翻倍直至达到最大退避上限。", category="传输引擎", ), ConfigField( key="transport.stream_reconnect_max_backoff_ms", type="int", required=False, default=30000, hot_reloadable=True, title="流重连最大退避", description="StreamWorker 断线重连指数退避的上限(毫秒),避免退避时间无限增长。", category="传输引擎", ), # FR-22 Outbox 重试策略(OBX-POLICY-UPDATE),JSON 格式: # {"max_retry": int, "ttl_seconds": int, "retry_backoff_schedule": [int, ...]} # 由 OutboxHandler.updateRetryPolicy 双写到 ConfigPort(Redis), # 支持跨请求 / 跨进程最终一致性。 ConfigField( key="outbox_retry_policy", type="json", required=False, default=None, hot_reloadable=True, title="Outbox 重试策略", description="出站消息重试策略,包含 max_retry / ttl_seconds / retry_backoff_schedule。", category="可靠性", ), # FR-22 Outbox Scanner-Worker 协调(Task 10):Scanner 判定 # SENT_UNCONFIRMED 无回执超时与 Worker 重试锁 TTL,原为硬编码 # 300s/60s,现统一收敛至 CONFIG_SCHEMA,支持热更新。 ConfigField( key="outbox_sent_unconfirmed_timeout_seconds", type="int", required=False, default=300, hot_reloadable=True, title="Outbox SENT_UNCONFIRMED 超时(秒)", description="Scanner 判定 SENT_UNCONFIRMED 消息无回执的超时秒数,超过此时间视为可能已发送但无回执", category="可靠性", ), ConfigField( key="outbox_retry_lock_ttl_seconds", type="int", required=False, default=60, hot_reloadable=True, title="Outbox 重试锁 TTL(秒)", description="Worker 单条消息重试的分布式锁 TTL,覆盖一次网络调用+DB持久化+事件发布的完整周期", category="可靠性", ), # === 渠道用户与 Agent 交互链路优化(Task 6/7/8)=== # 身份解析置信度阈值(账户级,scope=ACCOUNT),低于此阈值的解析结果视为 # 未命中,进入 channel_guest 身份生成路径。缺口补齐:原 _getThreshold # 使用硬编码默认值,现统一收敛至 CONFIG_SCHEMA。 ConfigField( key="identity_confidence_threshold", type="float", required=False, default=0.5, hot_reloadable=True, scope=ConfigScope.ACCOUNT, title="身份置信度阈值", description="身份解析结果低于此阈值时视为未命中,进入 channel_guest 生成路径。", category="身份与权限", ), # 新建 Agent 的默认渠道访问级别(全局级,scope=GLOBAL),取值如 # "none" / "read" / "write" 等,由 Agent 创建流程读取。 ConfigField( key="channel_access_default_level", type="str", required=False, default="none", hot_reloadable=True, scope=ConfigScope.GLOBAL, title="默认渠道访问级别", description="新建 Agent 默认的渠道访问级别,如 none / read / write。", category="身份与权限", ), # 渠道访客身份置信度(全局级,scope=GLOBAL),channel_guest 身份生成时 # 使用此置信度,低于 identity_confidence_threshold 以避免被误判为 # 已解析身份。 ConfigField( key="channel_guest_confidence", type="float", required=False, default=0.3, hot_reloadable=True, scope=ConfigScope.GLOBAL, title="访客身份置信度", description="channel_guest 身份生成时使用的置信度,应低于身份置信度阈值。", category="身份与权限", ), # 是否启用管理员手动绑定功能(全局级,scope=GLOBAL),控制渠道用户与 # 统一身份的手动绑定入口是否可用。 ConfigField( key="enable_channel_user_binding", type="bool", required=False, default=True, hot_reloadable=True, scope=ConfigScope.GLOBAL, title="手动绑定开关", description="是否启用管理员手动绑定渠道用户与统一身份的功能入口。", category="身份与权限", ), # === 渠道账号运营流程优化(Task 14:契约层 CONFIG_SCHEMA 健康检查配置键)=== # 以下 2 个键均为渠道级(scope=CHANNEL),归属 ConfigPort(Redis 热重载, # CHANNEL 业务策略体系),不得声明在 app_config。 # 凭证失效计数阈值。健康检查探活返回 401/403 时计入凭证失效计数, # 达阈值后调聚合根 markFailed。窗口外(credential_failure_window_seconds) # 重置计数。 ConfigField( key="credential_failure_threshold", type="int", required=False, default=3, hot_reloadable=True, scope=ConfigScope.CHANNEL, title="凭证失效阈值", description="健康检查中凭证失效计数的阈值。", category="健康检查", ), # 凭证失效计数窗口(秒)。窗口外的失效计数被重置,避免历史故障永久影响账号状态。 ConfigField( key="credential_failure_window_seconds", type="int", required=False, default=300, hot_reloadable=True, scope=ConfigScope.CHANNEL, title="凭证失效窗口", description="凭证失效计数的滑动窗口时长(秒)。", category="健康检查", ), # 审计日志保留策略(全局级,scope=GLOBAL)。JSON 格式: # {"retention_days": int, "cleanup_interval_hours": int} # 控制审计日志的保留天数与清理周期,由审计清理任务读取。 ConfigField( key="audit_retention_policy", type="json", required=False, default={"retention_days": 90, "cleanup_interval_hours": 24}, hot_reloadable=True, scope=ConfigScope.GLOBAL, title="审计保留策略", description="审计日志保留天数与清理周期配置,JSON 格式。", category="审计与登录", ), # 扫码登录重定向 URI(全局级,scope=GLOBAL)。扫码登录成功后的前端跳转地址, # 为空时使用默认跳转行为。 ConfigField( key="qr_login_redirect_uri", type="str", required=False, default="", hot_reloadable=True, scope=ConfigScope.GLOBAL, title="扫码登录跳转地址", description="扫码登录成功后的前端跳转地址。", category="审计与登录", ), # 渠道熔断器配置(GLOBAL 作用域),由 ChannelCircuitBreaker 读取。 # 包含连续失败阈值与恢复超时时间;未配置时使用组件内默认值。 ConfigField( key="circuit_breaker", type="json", required=False, default={"threshold": 5, "recovery_timeout": 60.0}, hot_reloadable=True, scope=ConfigScope.GLOBAL, title="渠道熔断器配置", description="渠道 API 熔断器参数,包含连续失败阈值与恢复超时时间。", category="可靠性", ), # === 不可热更新字段(hot_reloadable=False)=== ConfigField( key="webhook_port", type="int", required=False, default=None, hot_reloadable=False, title="Webhook 端口", description="渠道 Webhook 服务监听端口。", category="基础设施", ), ConfigField( key="webhook_secret", type="str", required=False, default=None, hot_reloadable=False, sensitive=True, title="Webhook 密钥", description="渠道 Webhook 签名验证密钥,敏感字段。", category="基础设施", ), ConfigField( key="tls_cert", type="str", required=False, default=None, hot_reloadable=False, sensitive=True, title="TLS 证书", description="TLS 证书内容,敏感字段。", category="基础设施", ), ConfigField( key="tls_key", type="str", required=False, default=None, hot_reloadable=False, sensitive=True, title="TLS 私钥", description="TLS 私钥内容,敏感字段。", category="基础设施", ), ConfigField( key="plugin_entry", type="str", required=False, default=None, hot_reloadable=False, title="插件入口", description="插件入口模块路径。", category="基础设施", ), ConfigField( key="database_url", type="str", required=False, default=None, hot_reloadable=False, sensitive=True, title="数据库地址", description="业务数据库连接地址,敏感字段。", category="基础设施", ), ConfigField( key="redis_url", type="str", required=False, default=None, hot_reloadable=False, sensitive=True, title="Redis 地址", description="Redis 连接地址,敏感字段。", category="基础设施", ), ConfigField( key="transport.max_restart_attempts", type="int", required=False, default=None, hot_reloadable=False, title="最大重启尝试", description="传输进程异常后的最大重启次数。", category="传输引擎", ), ConfigField( key="transport.graceful_shutdown_timeout_s", type="float", required=False, default=10.0, hot_reloadable=False, title="优雅关闭超时", description="传输进程优雅关闭的最大等待时间(秒)。", category="传输引擎", ), # === 渠道账号运营流程优化(Task 3:契约层 CONFIG_SCHEMA 凭证配置键)=== # 以下 3 个键均为账户级(scope=ACCOUNT),归属 ConfigPort(Redis 热重载, # CHANNEL 业务策略/toggles/TTLs 体系),不得声明在 app_config。 # 凭证包,敏感字段加密存储。由 CredentialService 唯一写入入口维护, # 包含 bot_token/app_secret/user_access_token 等渠道认证凭证。 ConfigField( key="credentials", type="dict", required=False, default=None, hot_reloadable=False, sensitive=True, scope=ConfigScope.ACCOUNT, title="渠道凭证包", description="渠道认证凭证集合,敏感字段。", category="凭证与登录态", ), # 登录态:unlogged/logging_in/logged_in/logout_failed。由 QrLoginService # 通过 ConfigPort 维护,区分扫码会话状态与最终凭证落库状态。 ConfigField( key="login_status", type="str", required=False, default="unlogged", hot_reloadable=False, scope=ConfigScope.ACCOUNT, title="登录状态", description="当前账户扫码登录状态。", category="凭证与登录态", ), # 凭证版本号,每次 rotateCredentials 自增。用于乐观锁控制与缓存失效判定。 ConfigField( key="credential_version", type="int", required=False, default=0, hot_reloadable=False, scope=ConfigScope.ACCOUNT, title="凭证版本", description="凭证轮换版本号,用于乐观锁与缓存失效。", category="凭证与登录态", ), # FR17-P0-3 已应用迁移列表(ACCOUNT 作用域,target=channel_type 或 # {channel_type}:{account_id})。由 PluginLifecycleManager 在插件 onStart # 成功后追加 manifest.version 标记配置已加载,DoctorService 只读不写。 # hot_reloadable=False:非业务策略,启动期写入,无需热更新。 # default=[] 让首次加载时 ConfigPort.get 返回空列表而非 NotFoundError, # 避免 PluginLifecycleManager 与 DoctorService 的 catch 块接不到异常。 ConfigField( key="applied_migrations", type="json", required=False, default=[], hot_reloadable=False, scope=ConfigScope.ACCOUNT, title="已应用迁移", description="账户已应用的插件迁移版本列表。", category="凭证与登录态", ), # 渠道命令声明(CHANNEL 作用域),由各渠道 CommandAdapter 读取。 # 存储命令列表 JSON,未配置时适配器回退为内置默认命令。 ConfigField( key="commands", type="json", required=False, default=None, hot_reloadable=True, scope=ConfigScope.CHANNEL, title="渠道命令列表", description="渠道自定义命令声明,未配置时使用内置默认命令。", category="功能开关", ), # 安装向导状态(CHANNEL 作用域),由 WizardService 读写。 # 存储已应用步骤的配置补丁聚合,支持跨会话恢复向导进度。 ConfigField( key="wizard_state", type="json", required=False, default={}, hot_reloadable=True, scope=ConfigScope.CHANNEL, title="向导状态", description="安装向导已应用步骤的配置补丁聚合,用于跨会话恢复向导进度。", category="运营流程", ), # 安装向导最终配置(CHANNEL 作用域),由 WizardService.finalize 写入。 # 存储向导聚合后的完整渠道配置,供账户创建后运行时读取。 ConfigField( key="channel_config", type="json", required=False, default=None, hot_reloadable=True, scope=ConfigScope.CHANNEL, title="渠道向导配置", description="安装向导 finalize 后写入的聚合渠道配置,供运行时读取。", category="运营流程", ), # 用户级访问令牌(ACCOUNT 作用域),由 LoginAdapter 在登出时读取。 # 企业微信 OAuth 无独立 user_access_token,此键供 logout 检测登录态存在性。 ConfigField( key="user_access_token", type="str", required=False, default=None, hot_reloadable=False, sensitive=True, scope=ConfigScope.ACCOUNT, title="用户访问令牌", description="用户级 OAuth 访问令牌,用于登录态检测与登出清理。", category="凭证与登录态", ), ) # 可热更新的配置项键集合(FR-37),从 CONFIG_SCHEMA 派生。 HOT_RELOADABLE_KEYS = frozenset(f.key for f in CONFIG_SCHEMA if f.hot_reloadable) # 不可热更新的配置项键集合(FR-37),从 CONFIG_SCHEMA 派生。 NON_HOT_RELOADABLE_KEYS = frozenset(f.key for f in CONFIG_SCHEMA if not f.hot_reloadable) __all__ = ["CONFIG_SCHEMA", "HOT_RELOADABLE_KEYS", "NON_HOT_RELOADABLE_KEYS"]